【问题标题】:Deadlock with only ONE Resource and Isolation Level Serializable...?只有一个资源和隔离级别可序列化的死锁......?
【发布时间】:2015-11-19 18:20:19
【问题描述】:

我使用实体框架来处理长时间运行的任务(平均 10-30 秒)。我有许多工作人员实例,每个工作人员从数据库表中获取下一个任务 ID,然后它会获取该 ID 的工作描述。

当然,对任务表的访问必须是序列化的,这样来自worker的每个请求都会得到一个新的id。我认为这样做可以:

static int? GetNextDetailId()
{
  int? id = null;
  using ( var ctx = Context.GetContext() )
    using ( var tsx = ctx.Database.BeginTransaction( System.Data.IsolationLevel.Serializable ))
    {
      var obj = ctx.DbsInstrumentDetailRaw.Where( x => x.ProcessState == ProcessState.ToBeProcessed ).FirstOrDefault();
      if ( obj != null )
      {
        id = obj.Id;
        obj.ProcessState = ProcessState.InProcessing;
        ctx.SaveChanges();
      }
      tsx.Commit();
    }

  return id;


} // GetNextDetailId

不幸的是,当我与 10 名工人一起运行它时,我几乎立即得到了

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

我对这种行为没有任何解释。我知道死锁情况:我们有两个或更多资源和两个或更多进程试图以不同的顺序获取资源。但是这里我们只有一个资源!我想要的只是可以顺序访问此资源的进程。因此,如果 A 有一个事务打开,B 应该简单地等到 A 提交/回滚。这似乎不会发生在这里。

有人可以吗

  1. 阐明这里发生了什么,以教育我。

  2. 为问题提供一个(“THE?”)解决方案。我认为这个问题在编程中应该很常见。

谢谢 马丁

【问题讨论】:

  • 默认隔离级别设置为可序列化,在使用中会产生不必要的阻塞和死锁。因此,建议您将默认隔离级别覆盖为 ReadCommitted,这反映了 SQL Server 中的默认值。
  • ... 对不起,这与问题无关。请问解决方法?
  • 不,Rookie,但只是建议它可以帮助减少死锁的数量,当你有更大程度的请求时。如果这是一个解决方案,会在下面发布。 :)

标签: c# entity-framework transactions database-deadlocks


【解决方案1】:

您可以使用 SQL Profiler 来验证这一点,以嗅探您的 SQL 服务器上正在执行的 SQL 语句,但问题可能是,即使您处于隔离级别设置为可序列化的事务中,排他锁也是没有发出,所以发生的是两个线程同时访问同一行,并且都在尝试更新它。

我见过的最好的建议是,如果您需要在此级别控制锁定,请执行存储过程或 SQL,而不是尝试使用 LINQ。

Locking a table with a select in Entity Framework

【讨论】:

    【解决方案2】:

    阐明这里发生了什么,以教育我。

    1. ctx.DbsInstrumentDetailRaw.Where ... 获取表上的共享锁。使用可序列化的隔离级别,此锁会一直保持到事务提交或回滚为止。
    2. ctx.SaveChanges() 需要排他锁来更新行。

    两个或多个事务可以在步骤 1 中同时获得共享锁,但在步骤 2 中它们都无法获得排他锁。死锁。

    给出问题的(“THE?”)解决方案。

    我可以想出两种方法来解决这个问题。

    1. 更改操作顺序:更新一行,然后返回。您必须使用存储过程在 EF 中执行此操作。
    2. 使用较低的隔离级别(例如可重复读取)和乐观并发。您不会遇到死锁(共享锁将在select 之后立即释放)。当 2 名工作人员尝试更新同一行时,其中一名工作人员将获得并发异常。

    【讨论】:

    • Tnx。我不知道具有可序列化隔离级别的两个事务可以同时读取相同的行......我想避免正是“可序列化”的目的。也许我需要一个更像是一行的 r/w 锁的东西。
    【解决方案3】:

    好的,我现在这样做:

    使用 (tablockx, holdlock) 从 InstrumentDetailRaw 中选择 1,其中 0 = 1"

    在我的交易开始时,根据这篇文章:

    Locking a table with a select in Entity Framework

    成功了。现在 10 名工人运行数小时没有死锁。

    【讨论】:

    • 这将锁定表,因此如果您针对该数据库运行多个工作人员,它将不得不等待另一个工作人员完成它正在做的事情。
    猜你喜欢
    • 2021-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多