Woo*_*Hoo 6 sql-server-2005 sql-server deadlock
我继承了一个 SQL Server 2005 数据库,该数据库每天出现 2-3 次死锁。
我已将其追踪到白天运行的计划作业,并使用触发器插入到表中。
触发器包含对另一个表的 10 次更新,这些更新的条件略有不同。死锁发生在触发器中。
当一个人提出申请并且工作正在运行时,就会发生死锁。应用程序插入到与预定作业相同的表中。
从跟踪来看,似乎发生在进程 1 获得键锁,进程 2 获得页锁,然后进程 1 将键锁升级为页锁并且进程 2 尝试获取键锁时发生的情况。
我添加了缺失的索引,这似乎有所帮助,但它仍在发生。我不是 DBA,因此对于解决此问题的方法的任何建议将不胜感激。
我添加了死锁 xml 的链接 - 这是我为复制问题所做的测试。
明确的解决方案需要 tableA 的创建脚本,包括所有索引和所涉及的两个 UPDATE 语句的执行计划。话虽如此,我们仍然可以将提供的死锁图与 SQL Server 如何执行更新的知识结合起来,并且很有可能解决这个问题。
此死锁涉及两个 spid 在 tableA、58 和 59 上执行更新。
Spid 58 的查询:
Update b set syd_id=d.syd_id
from tableA b
inner join tableC d with (nolock) on b.syd_pers_id=d.syd_pers_id
--and b.game='es_lotto'
and b.game=d.game and isnull(b.syd_id,0)=0
Run Code Online (Sandbox Code Playgroud)
Spid 59 的查询:
Update b set Draw_Date=d.draw_date
from tableA b
inner join tableB d with (nolock) on b.draw_no=d.draw_no
and left(b.game,2)='UK' and b.Draw_Date is null
Run Code Online (Sandbox Code Playgroud)
在这个僵局中,涉及到两个资源:
还使用了 3 种不同的锁所有权模式(U、X、IX):
现在,让我们详细检查 spid 58 的 UPDATE 查询。我们从死锁图中知道 spid 58 对 IX_tableA_Draw_Date 中的一行有排他锁。因此,我们知道 syd_id 要么在索引键中,要么在聚集索引键中,要么是一个包含列(我将忽略 spid 58 已经拥有触发器中前一个语句的锁的可能性)。
Spid 58 还试图在不同索引中的页面上获取更新锁。这说明了两件事:
我们还可以假设预计更新的行数相对较少(由于多列连接结合更新插入触发器中的空值或 0,这感觉不像是更新大量行的查询)。考虑到所有这些,很可能 UPDATE 正在使用对我们的神秘索引的扫描(不知道它是集群还是非集群)从 tableA 读取行,并针对 tableC 执行 LOOP JOIN 以获取 tableC.syd_id .
对 spid 59 执行类似的分析,很可能我们正在寻找“Draw_Date 为空”的 IX_tableA_Draw_Date,读取(并在 ROWS 上获取更新锁),对 tableB 进行 LOOP JOIN,然后获取 X ROW 锁并更新我们的“神秘”索引(页面上的 IX 锁意味着行上有 X 锁)。
我们在哪?我们接下来要做什么?
我们知道我们有两个更新语句。两者都在读取和写入同一对索引(读取和写入交换)。在他们已经更新了一些行之后,两者都在等待更新锁。Spid 58 是读取页面,59 是读取行。两者都是写入和锁定行。锁升级不是一个因素,因为行锁 -> 表锁(不是页锁)。
如果 Spid 58 在神秘索引上使用行锁而不是页锁,那么只有当两个查询都更新同一行时才会出现死锁。您可以使用 ROWLOCK 查询提示来实现这一点,但是您会面临将锁升级到表锁的风险,这可能会更糟。
另一种方法是将更新分成两部分。
select id, --Not sure what the PK is...
syd_id
into #tmp
from tableA inner join tableB (nolock) on b.syd_pers_id=d.syd_pers_id
--and b.game='es_lotto'
and b.game=d.game and isnull(b.syd_id,0)=0
UPDATE tableA SET syd_id = t.syd_id from tableA d inner join #tmp t on t.id = d.id
Run Code Online (Sandbox Code Playgroud)
这将更改锁定的顺序,因为非聚集索引只会为写入而不是读取锁定。
死锁是 RDBMS 的自然副产品。即使你做的一切都是“正确的”,你也不能总是消除它们。每天 3-4 次死锁,正确的方法可能是将语句包装在一些错误处理中,以便在发生死锁时重新尝试。
http://msdn.microsoft.com/en-us/library/aa175791(v=sql.80).aspx有一个 SQL 2005 示例。但是,由于这是在触发器中,您可能需要添加错误处理在触发触发器的 INSERT 语句之外。