【问题标题】:Transaction (Process ID xx) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction事务(进程 ID xx)与另一个进程在锁资源上死锁,并已被选为死锁牺牲品。重新运行事务
【发布时间】:2015-07-20 10:51:00
【问题描述】:

我要...

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 Profiler 中查看死锁图,但说实话我无法真正理解。

Thread.Sleep(2000) 我放在那里是为了模拟一个需要更长处理时间的事务;顺便说一句,这在某种程度上似乎是罪魁祸首,因为当我删除它时,我没有遇到任何僵局。有什么想法吗?

以下是死锁图:

【问题讨论】:

  • 您是否仅将Thread.Sleep() 用于测试目的?用它来模拟长时间运行的事务似乎很奇怪。
  • 您使用的是哪个隔离级别?如果您正在使用 Serializable 尝试更改为 Read Commited (如果它有效,请考虑这是否会影响您)如果您发布死锁图,那会很好
  • @Kamo 是的。我想不出其他选择。对此有什么建议吗?在交易中以这种方式使用它对我来说似乎也很奇怪。嗯。
  • @Juan 默认。哪个是可序列化的。
  • @PussInBoots - 如果你真的想模拟后台任务运行(我相信它是为了 UI/UX 目的)你可以使用简单的 setTimeout() 使用 JavaScript(如果你正在构建 Web 应用程序)。

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


【解决方案1】:

因此,从死锁图看来,由于 Serializable 隔离级别,您正在死锁。考虑一下这种情况:

  1. 请求 1 来并读取整个 PK 索引以找出最新的订单号。锁定它以供写入,因为它是可序列化的
  2. 请求 1 休眠 2 秒
  3. 请求 2 来并读取整个 PK 索引以找出最新的订单号。锁定它以供写入,因为它是可序列化的
  4. 请求 2 休眠 2 秒。
  5. 请求 1 唤醒并尝试写入,但 PK 索引被请求 2 锁定以进行写入。
  6. 请求 2 唤醒并尝试写入,但 PK 索引被请求 1 锁定以进行写入。

有不同的方法可以解决这个问题,这里有两个选项

  1. 为所有订单生成订单号,而不仅仅是成功的订单。您可以使用 IDENTITY 列在数据库上执行此操作,而无需读取。
  2. 将您的隔离级别更改为已提交读取,这样读取就不会阻塞表,但这意味着您的读取可能会变得“脏”并且您最终可能会生成重复的订单号。您可以在数据库上添加唯一键来处理它,如果违反约束,您可以重试。

一般而言,Serializable 不是一个很好的扩展隔离级别,如果您可以使用 Read Commited 或 Read Commited Snapshot 隔离级别更好地做到这一点。

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

如果您想获得连续的订单号(为什么?有一些空白应该没什么大不了的,并且会为您省去很多麻烦),您可能会遇到该订单号生成的瓶颈。您可以只使您的数据库端单线程并一次处理一个请求,因此您甚至不需要为此创建事务。但显然你会遇到可扩展性问题。

【讨论】:

    【解决方案2】:

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

    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();
    }
    

    如果在 linkTransactionOptionsIsolationLevel 中找到此代码。我希望这会对您有所帮助。

    【讨论】:

    • 是的,这就是我认为可以解决的问题。但他需要解决该代码的问题,即它可以生成重复的 OrderNumber,因为它正在读取最新的订单号而不是锁定。数据库生成的数字(身份或非身份)比手动生成更有意义。
    • @Juan 我不能使用常规标识列进行订单编号,因为我需要能够预测将生成的编号序列。 1,2,3,4,5 等 使用 OrderId 作为订单号可能会导致 SQL Server 中出现不可预知的结果。 1,2,3,4,5 然后突然可以跳到 1000,1001,1002 等。这是设计使然。
    • 我使用这种方法得到了重复的 OrderNumber 值。我真的需要该应用程序在该事务期间一次出现在该表中,以免获得重复的 OrderNumber 值。
    • 关于如何处理我回答中重复订单号的一些建议。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多