解决SQL Server死锁情况

时间:2010-03-15 17:56:26

标签: sql-server deadlock

我正在尝试找到一种解决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包含冲突的更新锁定)

4 个答案:

答案 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进程。