我正在尝试找到一种解决SQL Server中反复出现的死锁情况的解决方案。我已经对分析器跟踪生成的死锁图进行了一些分析,并得出了这些信息:
第一个进程(spid 58)正在运行此查询:
UPDATE cds.dbo.task_core
SET nstate = 1
WHERE nmboxid = 89 AND ndrawerid = 1
AND nobjectid IN (SELECT
nobjectid
FROM (SELECT
nobjectid, count(nobjectid) AS counting
FROM cds.dbo.task_core
GROUP BY nobjectid
) task_groups
WHERE task_groups.counting > 1
)
第二个进程(spid 86)正在运行此查询:
INSERT INTO task_core (…) VALUES (…)
spid 58 正在等待CDS.dbo.task_core上的共享页面锁定 (spid 86持有冲突的 intent exclusive(IX)锁定)
spid 86 正在等待CDS.dbo.task_core上的 Intent Exclusive(IX)页面锁定 (spid 58包含冲突的更新锁定)
答案 0 :(得分:2)
发布声明和资源是件好事。为了充分理解这个问题,这些计划也是有用的。但是我将做一个(受过教育的)猜测,并在UPDATE子查询中发生大扫描时诊断死锁原因:
SELECT nobjectid
FROM (SELECT nobjectid, count(nobjectid) AS counting
FROM cds.dbo.task_core
GROUP BY nobjectid
) task_groups
WHERE task_groups.counting > 1
此查询必须扫描整个 task_core表。总是。您正在访问页面上的死锁,因为全表扫描已优化为使用页锁定,但如果添加ROWLOCK提示,您也可以将其命中为行级别。
要消除死锁,必须消除更新中发生的全表扫描期间的冲突。您可以尝试使用行级版本控制,在数据库中启用读取提交的快照隔离,而不是使用脏读,请参阅Understanding Row Versioning-Based Isolation Levels。
但多更好的解决方案不是首先扫描。首先,重新审视业务逻辑要求和数据模型。每当您看到需要查看整个表的更新来做出决定时,这是一个非常非常臭的代码。如果您真的发现无法以更合理的方式重写更新(我怀疑),那么您应该考虑使用索引视图。索引视图中允许使用BIG_COUNT(*)表达式,除了消除死锁原因外,它们还会speed up the query significantly。
答案 1 :(得分:1)
关闭袖口,我猜你spid 58中最内层的子查询正在等待INSERT(spid 86)。
假设子查询中的脏读取正常,请尝试添加“WITH(NOLOCK)”。
(
SELECT nobjectid, count(nobjectid) AS counting
FROM cds.dbo.task_core WITH (NOLOCK)
GROUP BY nobjectid
)
答案 2 :(得分:1)
我怀疑,和其他人一样,可以改进查询部分的性能,以减少死锁的可能性。但是,我也怀疑在真正存在“需要”僵局的情况下可能存在合法且不可避免的情况。如果正在使用多部分密钥(例如,由于身份密钥而仅在表的末尾添加记录,或者它们是否正在使用),这在很大程度上取决于您的insert语句和一次添加的行数。由于复合键而在表中插入。)
我认为你可能在这里遇到一个真正问题的最好例子是:如果你插入的记录有nmboxid = 89 AND ndrawerid = 1
,并且反弹了objectid> 1?
我并不是要劝阻你调查对根本原因的正确解决方法;但另一方面,我想知道最简单的解决方案(至少作为第一步)是否在发生死锁时正确处理。
答案 3 :(得分:0)
您不需要在子查询中包含派生表,原始查询可以是:
UPDATE cds.dbo.task_core
SET nstate = 1
WHERE nmboxid = 89 AND ndrawerid = 1
AND nobjectid IN (SELECT
nobjectid
FROM cds.dbo.task_core
GROUP BY nobjectid
HAVING COUNT(nobjectid)>1
)
但这不会阻止僵局。你可以在子查询中添加WHERE吗?像:
UPDATE cds.dbo.task_core
SET nstate = 1
WHERE nmboxid = 89 AND ndrawerid = 1
AND nobjectid IN (SELECT
nobjectid
FROM cds.dbo.task_core
WHERE nmboxid = 89 AND ndrawerid = 1 --<<<<<<<
GROUP BY nobjectid
HAVING COUNT(nobjectid)>1
)
这可能(如果可以使用索引)阻止表扫描并允许INSERT进程。