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



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

当然,对任务表的访问必须序列化,这样来自工作者的每个请求都会得到一个新的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?")解决方案。我认为这个问题在编程中应该很常见。

谢谢Martin

您可以使用SQL事件探查器来探查SQL服务器上正在执行的SQL语句来验证这一点,但问题可能是,即使您在隔离级别设置为可序列化的事务中,也不会发出独占锁,因此会发生两个线程同时访问同一行的情况,两者都在尝试更新。

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

在实体框架中使用select锁定表

揭示这里发生了什么,来教育我。

  1. CCD_ 1获取该表上的共享锁。对于可序列化隔离级别,此锁将一直保持到事务提交或回滚为止
  2. ctx.SaveChanges()需要一个独占锁来更新该行

在步骤1中,两个或多个事务可以同时获得共享锁,但在步骤2中,它们都不能获得独占锁。僵局

给这个问题一个("THE?")解决方案。

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

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

好的,我现在做这个:

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

在我的交易开始时,根据这篇帖子:

使用实体框架中的选择锁定表

真的很管用。10名工人连续工作数小时,没有出现死锁。

最新更新