死锁只有一个资源和隔离级别可序列化......?

时间:2015-11-19 18:20:20

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

我使用Entity Framework来处理长时间运行的任务(平均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?”)解决方案。我认为这个问题在编程时应该很常见。

  3. 由于 马丁

3 个答案:

答案 0 :(得分:0)

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

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

Locking a table with a select in Entity Framework

答案 1 :(得分:0)

  

了解这里发生的事情,教育我。

  1. ctx.DbsInstrumentDetailRaw.Where ...获取表上的共享锁。使用可序列化的隔离级别,此锁定将一直保持,直到提交或回滚事务。
  2. ctx.SaveChanges()需要一个独占锁才能更新该行。
  3. 在步骤1中,两个或多个事务可以同时获得共享锁,但是在第2步中没有一个可以获得独占锁。死锁。

      

    为问题提供一个(" THE?")解决方案。

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

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

答案 2 :(得分:0)

好的,我现在这样做:

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

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

Locking a table with a select in Entity Framework

诀窍。 10名工人现在已经运行数小时没有死锁。