Ian*_*oyd 8 sql-server error-handling deadlock sql-server-2000 database-deadlocks
我有执行连接的一个存储过程TableB以TableA:
SELECT <--- Nested <--- TableA
Loop <--
|
---TableB
Run Code Online (Sandbox Code Playgroud)
同时,在事务中,行插入TableA,然后插入TableB.
这种情况偶尔会导致死锁,因为存储过程选择从TableB中获取行,而insert将行添加到TableA,然后每个都希望另一个放弃另一个表:
INSERT SELECT
========= ========
Lock A Lock B
Insert A Select B
Want B Want A
....deadlock...
Run Code Online (Sandbox Code Playgroud)
逻辑要求INSERT首先向A添加行,然后向B添加行,而我个人并不关心SQL Server执行其连接的顺序 - 只要它加入.
修复死锁的一般建议是确保每个人以相同的顺序访问资源.但在这种情况下,SQL Server的优化器告诉我相反的顺序是"更好".我可以强制执行另一个连接顺序,并且查询效果更差.
但是我应该吗?
我应该使用我希望它使用的连接顺序现在和永远覆盖优化器吗?
或者我应该只是捕获错误本机错误1205,并重新提交select语句?
问题不是当我覆盖优化器并且它做非理想的事情时,查询可能会执行多么糟糕.问题是:自动重试是否更好,而不是运行更糟糕的查询?
自动重试死锁是否更好?原因是你可能会解决这个僵局,以后再打另一个.如果表的大小发生更改,服务器硬件规格发生更改,并且服务器上的负载发生更改,则SQL发行版之间的行为可能会发生变化.如果死锁是频繁的,你应该采取积极的步骤来消除它(索引通常是答案),但对于罕见的死锁(比如每10分钟左右),在应用程序中重试可以掩盖死锁.您可以重试读取或写入,因为写入当然是由正确的begin transaction/commit事务包围,以使所有写入操作保持原子状态,从而能够在没有问题的情况下重试它们.
另一个需要考虑的途径是启用read committed snapshot.启用此选项后,SELECT将不会执行任何锁定,但会产生一致的读取.
为了避免死锁,最常见的建议之一是"以相同的顺序获取锁"或"以相同的顺序访问对象".显然,这是完全合理的,但它总是可行的吗?它总是可能吗?当我无法遵循这个建议时,我会遇到案件.
如果我将一个对象存储在一个父表和一个或多个子表中,我根本不能遵循这个建议.插入时,我需要先插入我的父行.删除时,我必须以相反的顺序执行.
如果我在一个表中使用触及多个表或多行的命令,那么通常我无法控制获取哪个顺序锁(假设我没有使用提示).
因此,在许多情况下,尝试以相同的顺序获取锁定并不能防止所有死锁.因此,无论如何我们需要某种处理死锁 - 我们不能假设我们可以完全消除它们.当然,除非我们使用Service Broker或sp_getapplock序列化所有访问.
当我们在死锁之后重试时,我们很可能会覆盖其他进程的更改.我们需要意识到,其他人很可能修改了我们想要修改的数据.特别是如果所有读者都在快照隔离下运行,那么读者就不能参与死锁,这意味着死锁中涉及的所有各方都是编写者,修改过或试图修改相同的数据.如果我们只是捕获异常并自动重试,我们可以覆盖其他人的更改.
这称为丢失更新,这通常是错误的.通常,在死锁之后执行的正确操作是在更高级别上重试 - 重新选择数据并决定是否以最初保存决策的方式进行保存.
例如,如果用户按下了Save按钮并且保存事务被选为死锁牺牲品,那么在死锁之后重新显示屏幕上的数据可能是个好主意.