事务(进程ID xx)在锁资源上与另一个进程死锁,并被选为死锁牺牲品。重新运行该交易

时间:2015-07-20 10:51:01

标签: c# sql entity-framework entity-framework-6 transactionscope

我得到了......

Transaction (Process ID xx) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

...在以下代码中。怎么样?

// SIMPLE ORDERNUMBER LOGIC
var orderNumber = 1;
Order order = null;

using (TransactionScope scope = new TransactionScope())
{
    if (db.Orders.Any(o => o.OrderNumber.HasValue))
    {
        // 1. Get the last successful order OrderNumber
        var lastSuccessfulOrder = db.Orders.Where(o => o.OrderNumber.HasValue).OrderByDescending(o => o.OrderNumber).FirstOrDefault();
        if (lastSuccessfulOrder != null)
        {
            orderNumber = lastSuccessfulOrder.OrderNumber.Value + 1;
        }
    }

    // 2. Create the new order with null values except OrderNumber column
    order = new Order();
    order.OrderNumber = orderNumber;
    db.Orders.Add(order);

    System.Threading.Thread.Sleep(2000);
    db.SaveChanges();
    scope.Complete();
}

我正在查看死锁图中的SQL事件探查器,但实际上我无法理解它。

Thread.Sleep(2000)我在那里模拟了一个需要花费更长时间来处理的事务;哪个btw似乎是某种方式的罪魁祸首,因为当我删除它时,我没有遇到任何僵局。有什么想法吗?

以下是死锁图: enter image description here enter image description here enter image description here

2 个答案:

答案 0 :(得分:1)

因此,由于Serializable隔离级别,死锁图看起来像是死锁。想想这个场景:

  1. 请求1来了,读取整个PK索引,找出最新的订单号。因为它是Serializable,所以将其锁定为写入
  2. 请求1睡2秒
  3. 请求2来了,读取了整个PK索引,找出了最新的订单号。因为它是Serializable,所以将它锁定为写入
  4. 请求2睡2秒钟。
  5. 请求1唤醒并尝试写入,但PK索引被请求2锁定以进行写入。
  6. 请求2唤醒并尝试写入,但PK索引被请求1锁定以进行写入。
  7. 您可以通过不同的方式解决此问题,这里有两个选项

    1. 为所有订单生成订单号,而不仅仅是成功的订单号。您可以使用IDENTITY列在数据库上执行此操作,但您不需要执行此操作。
    2. 将隔离级别更改为Read Committed,以便读取不会阻止该表,但这意味着您的读取可能变得“污染”并且#34;并且您最终可能会生成重复的订单号。您可以添加一个唯一键来处理数据库,如果违反了约束,您可以再次重试。
    3. 一般来说,如果你可以更好地使用Read Commited或Read Commited Snapshot隔离级别,那么Serializable不是一个好的扩展隔离级别。

      同样在代码上生成增量ID最好留给具有不同机制的数据库以避免锁定。

      如果你想拥有连续的订单号(为什么?有一些差距不应该是一个大问题,并且会为你节省很多麻烦),你可能会对订单号产生瓶颈。您可以让您的数据库端单线程并一次参加一个请求,这样您甚至不需要为此创建事务。但显然你会遇到可扩展性问题。

答案 1 :(得分:0)

我不是数据库专家,但在某些代码中可能会发生一些使用相同的表读/写。试试这个:

TransactionOptions transOptions = new TransactionOptions();
transOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;

var orderNumber = 1;
Order order = null;

using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew, transOptions))
{
    if (db.Orders.Any(o => o.OrderNumber.HasValue))
    {
        // 1. Get the last successful order OrderNumber
        var lastSuccessfulOrder = db.Orders.Where(o => o.OrderNumber.HasValue).OrderByDescending(o => o.OrderNumber).FirstOrDefault();
        if (lastSuccessfulOrder != null)
        {
            orderNumber = lastSuccessfulOrder.OrderNumber.Value + 1;
        }
    }

    // 2. Create the new order with null values except OrderNumber column
    order = new Order();
    order.OrderNumber = orderNumber;
    db.Orders.Add(order);

    System.Threading.Thread.Sleep(2000);
    db.SaveChanges();
    scope.Complete();
}

如果在此linkTransactionOptions& IsolationLevel。我希望这会对你有所帮助。