2015-10-20 85 views
4

我在SQL Server 2012和.NET 4.5.1上使用EntityFramework 6。长事务处理的死锁

在长时间运行的交易中,第二个用户发生死锁。问题是第一个用户阻止PayrollListHumanResourceID=90FA9981-AFD3-43BF-AD92-AAE5E2A42B5A的记录,第二个用户想要为PayrollListHumanResourceID=6CFE74C3-F180-497C-8DDA-BCA8D075FF59获取数据。

以下代码显示来自SQL事件探查器的实体框架客户端事务。对于第二个用户,最后(有时倒数第二个)exec发生死锁。为了举例,我删除了大多数可以正常工作的函数。我在C#代码中为DELETE部分之后和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; 

在具有表键和索引的图像下面。 PayrollListElements table

建立在我的数据库一般的表是:INT型

  • ID列,与聚集索引(现在已经过时了,对人友好的阅读),
  • 类型UNIQUEIDENTIFIER的GUID列,PRIMARY KEY,带非聚簇索引,
  • UNIQUEIDENTIFIER类型的外键,带非聚簇索引(当然指向GUID列),
  • 每个外键都有一个索引(除用于记录信息的2列外,他们没有使用在这次交易中)。

锁升级没有出现在我的例子中。 更改隔离级别不起作用。删除/由ID列更新该表工作没有任何僵局:

DELETE [Extent1] FROM [dbo].[PayrollListElements] AS [Extent1] WHERE [Extent1].ID = 30 

问题出现时行正在从被检查表和外键引用删除。当我从父表中删除一行时,所有的子引用都被检查。尽管所有外键都有一个非聚集索引,其中一些正在通过索引扫描而不是索引查找进行检查。如果在此操作期间另一个用户至少阻止扫描表中的一行 - 删除操作将被阻止。即使阻塞行没有删除数据的引用,也会发生这种情况。 使用FORCESEEK表提示不起作用。

Delete execution plan

Deadlock graph.xdl

Delete execution plan.sql

Trace profiler.trc

+1

捕获死锁图形XML并将其附加到此处。阅读[在SQL Server中捕获死锁](http://www.brentozar.com/archive/2014/06/capturing-deadlock-information/) –

+0

Remus Rusanu - 我刚刚编辑了我的帖子并附加了其他文件,包括死锁图表。 – Bruniasty

回答

2

我想你已经正确识别扫描的问题。扫描在有效的可序列化隔离下运行,以便检查后不会出现新行并违反FK。

即使有可能强制这些检查使用嵌套循环,我会建议反对。这看起来像一个相当脆弱和手动修复。事实上,我认为这不是一个完整的解决方案;它只会减少僵局的可能性。即使检查发现没有适用的行范围,钥匙锁仍然会在之前的钥匙上占用。你无法避免重叠。

到目前为止,我的最佳想法是实现最可能的死锁解决方案:一个重试循环,在SqlException.Number == 1205上重试。您必须重试整个交易。这总是有效并且安全。

另一种方法是使用像全局锁之类的东西来同时不运行潜在的冲突操作。这是核选择和最后的武器,因为它破坏了系统这一特定部分的可扩展性。

+0

另一种选择是使用队列进行长操作并调度序列化的队列执行(可选地使用全局/应用程序锁来确保串行行为)。这当然只能在用户不需要立即看到处理结果的情况下使用。 – Arvo

+0

感谢您的回答,但这并不能以便捷的方式解决我的问题。在这个例子中是否可以强制SQL不使用**索引扫描**? **我从表中删除了外键,并且没有更多的死锁。**虽然我不满意这个解决方案,但我无法想象,每个人都有这个问题,并且通过“解决方法”与它一起生活。这意味着SQL Server不会为具有多线程/多用户的实际项目中的事务,外键和索引提供任何有效的解决方案。 – Bruniasty

+0

您的最终结论有点太深远了......导致此处扫描的事情是,扫描的表非常小,输入恰好已经排序并且存在正确排序的索引。这显然是成本最低的计划。非常高效的计划!正如我所解释的,循环连接不会解决问题。你同意吗? – usr