我在SQL Server 2012和.NET 4.5.1上使用EntityFramework 6.
在长期运行期间,第二个用户发生死锁.问题是第一个用户阻止PayrollListHumanResourceID = 90FA9981-AFD3-43BF-AD92-AAE5E2A42B5A的记录,第二个用户想要获取PayrollListHumanResourceID = 6CFE74C3-F180-497C-8DDA-BCA8D075FF59的数据.
以下代码显示了SQL Profiler中针对Entity Framework客户端的事务.对于第二个用户,最后一个(有时是倒数第二个)exec会死锁.为了举例,我删除了大多数工作正常的函数.我在DELETE部分之后和C#代码中的COMMIT之前为第一个用户设置了断点.第一个用户具有不同的p__linq值.
set quoted_identifier on
set arithabort off
set numeric_roundabort off
set ansi_warnings on
set ansi_padding on
set ansi_nulls on
set concat_null_yields_null on
set cursor_close_on_commit off
set implicit_transactions off
set language us_english
set dateformat mdy
set datefirst 7
set transaction isolation level read uncommitted
begin tran;
(...)
exec sp_executesql N'DELETE [Extent1] FROM [dbo].[PayrollListErrors] AS [Extent1] WHERE [Extent1].[PayrollListHumanResourceID] = @p__linq__0',N'@p__linq__0 uniqueidentifier',@p__linq__0='6CFE74C3-F180-497C-8DDA-BCA8D075FF59'
exec sp_executesql N'DELETE [Extent1] FROM [dbo].[PayrollListElementRelations] AS [Extent1] WHERE [Extent1].[PayrollListHumanResourceID] = @p__linq__0',N'@p__linq__0 uniqueidentifier',@p__linq__0='6CFE74C3-F180-497C-8DDA-BCA8D075FF59'
exec sp_executesql N'DELETE [Extent1] FROM [dbo].[PayrollListElements] AS [Extent1] WHERE [Extent1].[PayrollListHumanResourceID] = @p__linq__0',N'@p__linq__0 uniqueidentifier',@p__linq__0='6CFE74C3-F180-497C-8DDA-BCA8D075FF59'
commit;
我数据库中构建的通用表是:
> INT类型的ID列,带有Clustered Index(现已过时,用于人性化阅读),
> UNIQUEIDENTIFIER,PRIMARY KEY类型的GUID列,带有非聚集索引,
> UNIQUEIDENTIFIER类型的外键,带有非聚集索引(当然指向GUID列),
>每个外键都有一个索引(除了用于日志信息的2列,但它们不在此事务中使用).
锁定升级不会出现在我的示例中.更改隔离级别无效.通过ID列删除/更新此表可以在没有任何死锁的情况下运行:
DELETE [Extent1] FROM [dbo].[PayrollListElements] AS [Extent1] WHERE [Extent1].ID = 30
从表中删除行并检查外键引用时会出现问题.当我从父表中删除行时,正在检查所有子引用.尽管所有外键都具有非聚集索引,但是其中一些通过索引扫描而不是索引搜索进行检查.如果在此操作期间,另一个用户阻止扫描表中的至少一行 – 将阻止删除操作.即使阻塞行没有删除数据的引用,也会发生这种情况.使用FORCESEEK表提示无效.
最佳答案 我认为你已经正确地将扫描识别为问题.扫描在有效的可序列化隔离下运行,因此在检查后不会出现新行并违反FK.
即使可以强制这些检查使用嵌套循环,我也会建议不要这样做.这似乎是一个相当脆弱和手动修复.事实上,我认为这不是一个完整的解决方案;它只会减少死锁的可能性.即使检查发现没有适用的行范围,仍将对前一个密钥进行密钥锁定.你无法避免重叠.
到目前为止,我最好的想法是实现最常见的死锁解决方案:在SqlException.Number == 1205上重试的重试循环.您必须重试整个事务.这总是有效并且安全.
另一种方法是使用诸如全局锁之类的东西来不同时运行可能存在冲突的操作.这是核选项和最后的武器,因为它破坏了系统这个特定部分的可扩展性.