Sql server SQL Server死锁修复:强制加入顺序,还是自动重试?

Sql server SQL Server死锁修复:强制加入顺序,还是自动重试?,sql-server,error-handling,sql-server-2000,deadlock,database-deadlocks,Sql Server,Error Handling,Sql Server 2000,Deadlock,Database Deadlocks,我有一个存储过程,它将TableB连接到TableA: SELECT <--- Nested <--- TableA Loop <-- | ---TableB 逻辑要求INSERT首先将行添加到A,然后添加到B,而我个人不在乎SQL Server执行其联接的顺序,只要它联接即可 修复死锁的常见建议是确保每个人都以相同的顺序访问资源。但在本例中,SQLServ

我有一个存储过程,它将
TableB
连接到
TableA

 SELECT <--- Nested <--- TableA
             Loop   <--
                      |
                      ---TableB
逻辑要求
INSERT
首先将行添加到A,然后添加到B,而我个人不在乎SQL Server执行其联接的顺序,只要它联接即可

修复死锁的常见建议是确保每个人都以相同的顺序访问资源。但在本例中,SQLServer的优化器告诉我相反的顺序是“更好”。我可以强制执行另一个联接顺序,而查询的性能会更差

但我应该吗

我应该用我希望它使用的连接顺序覆盖优化器吗

或者我应该捕获错误本机错误1205,然后重新提交select语句吗


问题不在于当我重写优化器并让它做一些非最优的事情时,查询的性能会差多少。问题是:自动重试是否比运行更糟糕的查询更好?

捕获和重新运行可以工作,但您确定SELECT始终是死锁受害者吗?如果insert是死锁的牺牲品,那么在重试时必须更加小心

我认为,在这种情况下,最简单的解决方案是取消锁定或重新提交(相同的事情)您的选择。人们有理由担心脏读,但我们已经在各地运行NOLOCK以获得更高的并发性多年了,从来没有出现过问题


我还将对锁语义进行更多的研究。例如,我相信如果您将事务隔离级别设置为snapshot(需要2005年或更高版本),您的问题就会消失

自动重试死锁是否更好。原因是您可能会修复此死锁,但稍后会遇到另一个死锁。如果表的大小改变,服务器硬件规格改变,甚至服务器上的负载改变,SQL版本之间的行为可能会改变。如果死锁很频繁,您应该采取积极的措施消除它(索引通常是答案),但是对于罕见的死锁(比如每10分钟左右),在应用程序中重试可以掩盖死锁。您可以重试读取或写入操作,因为写入操作当然由适当的begin transaction/commit transaction包围,以保持所有写入操作的原子性,从而能够在没有问题的情况下重试它们


另一个需要考虑的途径是开始。启用此选项后,SELECT将不接受任何锁,但会产生一致的读取。

为避免死锁,最常见的建议之一是“以相同的顺序获取锁”或“以相同的顺序访问对象”。显然,这很有道理,但它总是可行的吗?这总是可能的吗?当我不能遵循这个建议时,我经常遇到这样的情况

如果我将一个对象存储在一个父表和一个或多个子表中,我根本无法遵循这个建议。插入时,我需要先插入父行。删除时,我必须按相反的顺序执行

如果我使用的命令涉及多个表或一个表中的多行,那么通常我无法控制获取顺序锁的方式(假设我没有使用提示)

因此,在许多情况下,试图以相同的顺序获取锁并不能防止所有死锁。因此,我们无论如何都需要某种处理死锁的方法——我们不能假设我们可以消除所有死锁当然,除非我们使用Service Broker或sp_getapplock序列化所有访问。

当我们在死锁后重试时,很可能会覆盖其他进程的更改。我们需要意识到,很可能有其他人修改了我们打算修改的数据。特别是如果所有读卡器都在快照隔离下运行,那么读卡器就不能陷入死锁,这意味着死锁中涉及的所有各方都是写入者、修改者或试图修改相同的数据。如果我们只是捕获异常并自动重试,我们就可以覆盖其他人的更改

这称为丢失的更新,这通常是错误的。通常,在死锁发生后,正确的做法是在更高的级别上重试—重新选择数据并决定是否以与原始保存决定相同的方式进行保存


例如,如果用户按下保存按钮,并且保存事务被选为死锁受害者,则最好在死锁发生后在屏幕上重新显示数据

SQL Server使用最少的保留资源回滚事务。“插入”是一系列交易,可能有十几个插入。select是一个单独的select(包装在存储过程中)@Ian Boyd:single
select
语句不能创建死锁情况。您需要至少有两个多语句事务。它们不必都是DML,但它们都必须等待对方资源上的锁,这意味着它们都必须使用至少两个资源。如果它实际上只是一个
SELECT
语句,没有包装在任何更大的事务中,那么它可能不是真正的死锁,可能只是I/O系统难以跟上或其他一些奇怪的服务器问题。@Aaronaught。一次选择可能会导致另一个进程出现死锁(),在30天内,我记录了140个死锁受害者。其中138个在一个select语句中。1位于另一个select语句上。1正在进行事务性多语句更新。因此,为了回答您的问题(“您确定SELECT始终是死锁受害者吗?”):我非常确定。99.2857%肯定。@伊恩·博伊德:有趣,我从未经历过,今天学到了一些东西。在索引和查询结构方面,它似乎确实需要非常具体的环境组合。当然,
SELECT
语句总是被选为牺牲品,因为它被视为不太“重要”的事务。我是一名医生
INSERT     SELECT
=========  ========
Lock A     Lock B
Insert A   Select B
Want B     Want A
....deadlock...