实体框架死锁和并发

use*_*663 9 .net vb.net sql-server entity-framework

我们在实体框架6和SqlSever 2012的数据库第一模型中大量使用实体框架.

我们有许多相当长的运行进程(10秒),每个进程创建一个具有不同数据的相同类型的对象,这些对象在创建时使用实体框架在数据库中写入和删除数据.到现在为止还挺好.为了提高应用程序的性能,我们希望并行运行这些操作,因此使用该Task构造来实现如下:

Private Async Function LongRunningProcessAsync(data As SomeData) As Task(Of LongRunningProcessResult)
    Return Await Task.Factory.StartNew(Of LongRunningProcessResult)(Function()
                                                       Return Processor.DoWork(data)
                                                     End Function)             
End Function
Run Code Online (Sandbox Code Playgroud)

我们运行其中的10个并等待它们全部完成使用 Task.WaitAll

Class Processor
    Public Function DoWork(data As SomeData) As LongRunningProcessResult
        Using context as new dbContext() 
           ' lots of database calls 
           context.saveChanges()
        end Using

        ' call to sub which creates a new db context and does some stuff
        doOtherWork()

        ' final call to delete temporary database data
        using yetAnotherContext as new dbContext()
            Dim entity = yetAnotherContext.temporaryData.single(Function(t) t.id = me.Id)
            yetAnotherContext.temporaryDataA.removeAll(entity.temporaryDataA)
            yetAnotherContext.temporaryDataB.removeAll(entity.temporaryDataB)
            yetAnotherContext.temporaryData.remove(entity)

            ' dbUpdateExecption Thrown here
            yetAnotherContext.SaveChanges()
        end using
    End Function
End Class
Run Code Online (Sandbox Code Playgroud)

这很好用〜90%的时候其他10%的数据库服务器因内部死锁异常而死锁

所有处理器使用相同的表,但在进程之间绝对没有数据共享(并且不依赖于相同的FK行)并创建它们自己的实体框架上下文,它们之间没有共享交互.

回顾Sql Server实例的分析行为,我们在每个成功的查询之间看到大量非常短暂的锁定获取和释放.导致最终的死锁链:

Lock:Deadlock Chain Deadlock Chain SPID = 80 (e413fffd02c3)         
Lock:Deadlock Chain Deadlock Chain SPID = 73 (e413fffd02c3)     
Lock:Deadlock Chain Deadlock Chain SPID = 60 (6cb508d3484c) 
Run Code Online (Sandbox Code Playgroud)

锁本身是类型KEY,死锁查询都是针对同一个表但具有不同的表单键:

exec sp_executesql N'DELETE [dbo].[temporaryData]
WHERE ([Id] = @0)',N'@0 int',@0=123
Run Code Online (Sandbox Code Playgroud)

我们对实体框架相对较新,并且无法确定看似过度范围的锁的根本原因(我无法通过sql profiler识别被锁定的确切行).

编辑:deadlock.xdl

EDIT2:saveChanges在每个remove语句之后调用删除死锁仍然不太明白为什么它会死锁

HBo*_*omb 8

您似乎是Lock Escalation的受害者

为了提高性能,Sql Server(以及所有现代数据库引擎)将许多低级细粒度锁转换为几个高级粗粒锁.在您的情况下,它超过阈值后从行级锁定到完整表锁定.您可以通过以下几种方式解决此问题:

  1. 一种解决方案是调用已经完成的SaveChanges().这将更快地释放锁定,防止锁定升级发生,因为锁定计数降低=不太可能达到升级阈值.
  2. 您还可以将隔离级别更改为未提交读取,这将通过允许脏读取来降低锁定数量,这也会阻止锁定升级的发生.
  3. 最后,您应该能够使用SET(LOCK_ESCALATION = {AUTO | TABLE | DISABLE})发送ALTER TABLE命令.但是,即使禁用了表级锁也仍然可以.MSDN指向在可序列化隔离级别下扫描没有聚簇索引的表的示例.请看这里:https: //msdn.microsoft.com/en-us/library/ms190273(v = sql.110).aspx

在您的情况下,您调用保存更改的解决方案,导致提交事务和释放锁是更好的选择.