我使用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提交/回滚。这似乎不会发生在这里。
有人可以
了解了这里发生的事情,教育我。
为问题提供(“THE?”)解决方案。我认为这个问题在编程时应该很常见。
由于 马丁
答案 0 :(得分:0)
您可以使用SQL事件探查器验证这一点,以嗅探SQL服务器上正在执行的SQL语句,但问题可能是,即使您处于隔离级别设置为可序列化的事务中,独占锁也是没有被发布,所以发生的是两个线程同时访问同一行,并且都试图更新它。
我见过的最好建议是,如果你需要在这个级别控制锁定,请执行存储过程或SQL,而不是尝试使用LINQ。
答案 1 :(得分:0)
了解这里发生的事情,教育我。
ctx.DbsInstrumentDetailRaw.Where ...
获取表上的共享锁。使用可序列化的隔离级别,此锁定将一直保持,直到提交或回滚事务。ctx.SaveChanges()
需要一个独占锁才能更新该行。在步骤1中,两个或多个事务可以同时获得共享锁,但是在第2步中没有一个可以获得独占锁。死锁。
为问题提供一个(" THE?")解决方案。
我可以想出解决这个问题的两种方法。
select
之后立即释放)。当2个工作程序尝试更新同一行时,其中一个将获得并发异常。答案 2 :(得分:0)
好的,我现在这样做:
从InstrumentDetailRaw中选择1,使用(tablockx,holdlock),其中0 = 1“
在我的交易开始时,根据这篇文章:
Locking a table with a select in Entity Framework
诀窍。 10名工人现在已经运行数小时没有死锁。