如何自动重新运行死锁事务?(ASP.NET MVC/SQL Server)

7

我在ASP.NET MVC/SQL Server上有一个非常受欢迎的网站,不幸的是经常出现死锁。虽然我正在尝试通过SQL分析器找出死锁原因,但我想知道如何更改SQL Server在发生死锁时的默认行为。

是否可以重新运行导致问题的事务,而不是显示错误屏幕?

5个回答

12

Remus的回答存在根本性缺陷。根据 https://dev59.com/h3VD5IYBdhLWcg3wBm5h#112256 ,一致的锁定顺序不能防止死锁。我们所能做的最好的是减少它们发生的频率。

他在两个方面是错误的:

  1. 暗示死锁可以被预防。Microsoft和IBM都发布了有关减少死锁频率的文章,但没有地方声称可以完全预防死锁。
  2. 所有死锁都需要重新评估状态并做出新决策的含义是错误的。只要您在应用程序级别上回到决策点,就可以正确地重试某些操作。

副笔记: Remus的主要观点是数据库无法自动代表您重试操作,在这一点上他是完全正确的。但这并不意味着重新运行操作是对死锁的错误响应。


7
你的做法是错的,你永远无法通过SQL引擎来完成自动死锁重试,这个概念本质上就是错误的。死锁的定义就是你基于的状态已经变化了,因此你需要重新读取状态并做出新的决定。如果你的进程发生了死锁,根据定义,另一个进程已经赢得了死锁,并且它改变了你所读取的内容。
你唯一的关注点应该集中在找出死锁发生的原因并消除它。通常情况下,原因都会被证明是查询扫描了太多的数据。虽然其他类型的死锁也可能发生,但我打赌这不是你的情况。许多问题都将通过部署适当的索引来解决。有些问题会让你开始重新思考自己的需求。
有很多资源可以用来识别和解决死锁: 你还可以考虑使用快照隔离级别,因为与快照相关的无锁读取减少了死锁可能发生的范围(即只有写-写死锁可能发生)。请参见使用基于行版本的隔离级别

4
很多死锁的发生通常表明您没有正确的索引和/或统计数据已过时。您是否有定期计划的索引重建作为维护的一部分?
当出现错误1205(死锁发生)时,您的保存代码应自动重试保存。这里有一个标准模式:
catch (SqlException ex) 
{ 
    if (ex.Number == 1205) 
    { 
        // Handle Deadlock by retrying save...
    } 
    else 
    {
        throw; 
    }
} 

另一个选项是在您的存储过程中重试。这里有一个示例:使用Transact-SQL中的TRY...CATCH

是的,我知道:我的逻辑肯定有问题。但问题是,我该如何改变服务器的行为呢? - Alex
1
他并不是在说你的代码有问题。他是在建议你检查数据库表中的索引,然后重新创建或添加新的索引。 - Chad Ruppert
好的...那么另一个问题。假设死锁发生在页面A上,但是页面B正在尝试访问被锁定的数据。错误将显示在页面B上,但这并不意味着死锁发生在页面B上。它仍然发生在页面A上。如果我理解有误,请见谅。这个概念对我来说真的很难理解。 - Alex

1
除了Mitch和Remus提出的建议之外,还有一个选项,因为您的评论表明您正在寻找快速解决方案。如果您可以确定死锁中涉及的查询,则可以通过为每个查询、批处理或存储过程设置DEADLOCK_PRIORITY来影响哪些查询被回滚,哪些继续。
看看您在Mitch答案的评论中提供的示例:
假设死锁发生在页面A上,但页面B正在尝试访问已锁定的数据。错误将显示在页面B上,但这并不意味着死锁发生在页面B上。它仍然发生在页面A上。
如果您始终从页面A和页面B发出导致死锁的查询,则可以影响哪个页面会产生错误,哪个页面会成功完成。正如其他人所说,您无法自动强制重试。
发布一个带有问题查询和/或死锁跟踪输出的问题,并且很有可能会得到关于为什么会发生以及如何修复的解释。

谢谢。问题在于死锁发生在存储过程中。SQL Profiler 显示了该过程的名称,但我无法获取它发生的查询,而那里有很多查询。我该如何跟踪问题发生的查询? - Alex
1
您可以启用跟踪标志1204和1222,这两个标志都在Remus链接的“检测死锁”文章中有介绍。这将为您提供查询和资源的详细信息。将报告每个进程所涉及的行、页、索引或表以及所持有和所需的锁类型。通过这些信息,您可能能够确定解决方法,否则请发布一个带有死锁跟踪和死锁程序来源的问题。您无法修改出现问题的存储过程吗? - Mark Storey-Smith
不,我可以修改存储过程。我会查看文章并启用标志。希望它能够正常工作。 - Alex
好的,我启用了标志,现在我可以看到死锁发生在存储过程中的哪里。但是我在其他页面上看不到冲突查询。而且由于它发生在20-30个页面中的大多数上,这就是成千上万的查询!我已经对这个问题感到烦透了... - Alex
1
请更新您的问题,提供更详细的描述。您能用伪代码勾勒出功能吗? - Mark Storey-Smith
我成功获取了死锁产生的信息。我在这里发布了有关导致死锁的查询的详细信息:https://dev59.com/k07Sa4cB1Zd3GeqP1CIv - Alex

0
在某些情况下,您可以执行以下操作。在begin tran和commit之间是全有或全无的。因此,要么@errorcode取0作为值并结束循环,要么在失败的情况下将计数器减1并重试。如果您从begin tran/commit外部提供变量给代码,则可能无法正常工作。这只是一个想法 :)
declare @errorcount int = 4 -- retry number
while @errorcount >0
begin
begin tran 
<your code here>
set @errorcount =0
commit 
set @errorcount=@errorcount-1
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接