【问题标题】:How can I avoid a deadlock with this single UPDATE statement, even when using TABLOCKX?即使在使用 TABLOCKX 时,如何使用这个单一的 UPDATE 语句来避免死锁?
【发布时间】:2013-12-11 19:51:07
【问题描述】:

在 Trace flag 1222 的帮助下,我对处理和解决死锁有了很好的处理,TABLOCKX 也很有帮助。我看到一个新的死锁,我不明白它为什么会死锁,以及如何解决它。我使用的是 SQL Server 2008 R2,10.50.2861.0。

这个单一的语句本身就在一个事务中(我意识到不需要显式的 BEGIN/COMMIT 语句)。根据死锁跟踪(标志 1222),此语句正在锁定 TableB,试图获得锁定 TableA。 TableC 无关紧要。

我原以为 SQL Server 在获得TableATableB 的独占表锁之前不会开始处理此语句。如果它这样做了,我希望它推迟做任何事情(被阻止),直到它可以在两个表上获得排他锁。相反,SQL Server 似乎在到达 TableA 之前开始读取 TableB(并锁定它),然后当它到达 TableA 时,它发现自己与另一个进程(不同的 SQL 语句)死锁锁定了TableA,并且其他进程正在尝试将数据插入TableB。这个其他进程没有使用任何 TABLOCKX。

我对此的解释正确吗?如何让 SQL Server 在运行此语句之前锁定两个表以避免死锁?

UPDATE a
SET StatusId = 9,
    StatusLastUpdatedOn = GETDATE()
FROM dbo.TableA AS a WITH (TABLOCKX)
INNER JOIN dbo.TableC AS c ON c.StatusId = a.StatusId       
WHERE c.IsComplete = 0
AND   a.StatusId NOT IN (1, 3)
AND EXISTS
(
    SELECT *
    FROM dbo.TableB AS b WITH (TABLOCKX)
    WHERE b.Value1 = a.Value1
    AND   b.ConditionA = 1
);

编辑,根据@Bogdan Sahlean 的请求,以下是 TF1222 输出 - 缩写较长的语句。上面的 UPDATE 语句是 process4583288。它与 process459d708 陷入僵局。上面的 UPDATE 语句 (process4583288) 似乎是 TableB 的所有者,正在等待访问 TableA

12/11/2013 13:11:09,spid19s,Unknown,waiter id=process459d708 mode=IS requestType=wait
12/11/2013 13:11:09,spid19s,Unknown,waiter-list
12/11/2013 13:11:09,spid19s,Unknown,owner id=process4583288 mode=X
12/11/2013 13:11:09,spid19s,Unknown,owner-list
12/11/2013 13:11:09,spid19s,Unknown,objectlock lockPartition=0 objid=980471563 subresource=FULL dbid=11 objectname=dbname.dbo.TableB id=lock9e7bc880 mode=X associatedObjectId=980471563
12/11/2013 13:11:09,spid19s,Unknown,waiter id=process4583288 mode=X requestType=wait
12/11/2013 13:11:09,spid19s,Unknown,waiter-list
12/11/2013 13:11:09,spid19s,Unknown,owner id=process459d708 mode=IX
12/11/2013 13:11:09,spid19s,Unknown,owner-list
12/11/2013 13:11:09,spid19s,Unknown,objectlock lockPartition=0 objid=353147353 subresource=FULL dbid=11 objectname=dbname.dbo.TableA id=lock3f439dc80 mode=IX associatedObjectId=353147353
12/11/2013 13:11:09,spid19s,Unknown,resource-list
12/11/2013 13:11:09,spid19s,Unknown,Proc [Database Id = 11 Object Id = 1385795344]
12/11/2013 13:11:09,spid19s,Unknown,inputbuf
12/11/2013 13:11:09,spid19s,Unknown,EXEC dbo.usp_ProcedureB;
12/11/2013 13:11:09,spid19s,Unknown,frame procname=dbname.dbo.usp_ProcedureD line=127 stmtstart=6586 stmtend=7022 sqlhandle=0x03000b00108f9952edd5580183a200000100000000000000
12/11/2013 13:11:09,spid19s,Unknown,*** This is the INSERT statement that is deadlocking with the UPDATE statement posted in the Stackoverflow Question - shortened ***
12/11/2013 13:11:09,spid19s,Unknown,frame procname=dbname.dbo.usp_ProcedureB line=119 stmtstart=4972 stmtend=9598 sqlhandle=0x03000b00eb973e0a22e7ee007da200000100000000000000
12/11/2013 13:11:09,spid19s,Unknown,executionStack
12/11/2013 13:11:09,spid19s,Unknown,process id=process459d708 taskpriority=0 logused=236 waitresource=OBJECT: 11:980471563:0  waittime=2127 ownerId=23648431472 transactionname=user_transaction lasttranstarted=2013-12-11T13:11:07.233 XDES=0x27a62c3b0 lockMode=IS schedulerid=4 kpid=2788 status=suspended spid=61 sbid=0 ecid=0 priority=0 trancount=2 lastbatchstarted=2013-12-11T13:11:07.233 lastbatchcompleted=2013-12-11T13:11:07.223 clientapp=.Net SqlClient Data Provider hostname=IP-0AF81DC9 hostpid=5388 loginname=some-user isolationlevel=read committed (2) xactid=23648431472 currentdb=11 lockTimeout=4294967295 clientoption1=673185824 clientoption2=128056
12/11/2013 13:11:09,spid19s,Unknown,Proc [Database Id = 11 Object Id = 200150858]
12/11/2013 13:11:09,spid19s,Unknown,inputbuf
12/11/2013 13:11:09,spid19s,Unknown,EXEC dbo.usp_ProcedureA;
12/11/2013 13:11:09,spid19s,Unknown,frame procname=dbname.dbo.usp_ProcedureC line=56 stmtstart=2452 stmtend=2616 sqlhandle=0x03000b004a0fee0be3f6ee007da200000100000000000000
12/11/2013 13:11:09,spid19s,Unknown,*** This is the UPDATE statement Statement Posted in the Stackoverflow Question - shortened ***
12/11/2013 13:11:09,spid19s,Unknown,frame procname=dbname.dbo.usp_ProcedureA line=148 stmtstart=7390 stmtend=8462 sqlhandle=0x03000b00806122731562150182a200000100000000000000
12/11/2013 13:11:09,spid19s,Unknown,executionStack
12/11/2013 13:11:09,spid19s,Unknown,process id=process4583288 taskpriority=0 logused=0 waitresource=OBJECT: 11:353147353:0  waittime=2170 ownerId=23648431512 transactionname=user_transaction lasttranstarted=2013-12-11T13:11:07.240 XDES=0x17d90d950 lockMode=X schedulerid=3 kpid=1164 status=suspended spid=66 sbid=0 ecid=0 priority=0 trancount=2 lastbatchstarted=2013-12-11T13:11:07.030 lastbatchcompleted=2013-12-11T13:11:07.030 clientapp=.Net SqlClient Data Provider hostname=IP-0AF81DC9 hostpid=5388 loginname=some-user isolationlevel=read committed (2) xactid=23648431512 currentdb=11 lockTimeout=4294967295 clientoption1=673185824 clientoption2=128056
12/11/2013 13:11:09,spid19s,Unknown,process-list
12/11/2013 13:11:09,spid19s,Unknown,deadlock victim=process4583288
12/11/2013 13:11:09,spid19s,Unknown,deadlock-list

【问题讨论】:

  • 请贴出TF1222的输出。根据这个输出,有人可以说解释是正确还是错误。
  • 锁定 X 整个表只是为了避免死锁通常是一个糟糕的解决方案。
  • @Bogdan Sahlean,我编辑了我的问题,包括 TF1222 输出。我了解 TABLOCKX 不是您想要做的第一件事,通常我不会使用它们,但是没有它们,我会在一些经常读取和更新的表上遇到各种死锁,并且大部分解决了它们结合减少事务中的语句数量等,并发现 TABLOCKX 在一些地方提供帮助。比起死锁,我宁愿有少量的阻塞。
  • TABLOCKX 对并发有负面影响。此提示将独占锁定整个表。这可能会删除一些 DL,但这并不意味着这是解决方案。这只是一种避免真正问题而不是消除问题的方法。
  • 您能否发布这两个存储过程的执行计划(估计的或更好的实际执行计划):EXEC dbo.usp_ProcedureB & EXEC dbo.usp_ProcedureA?

标签: sql-server sql-server-2008 deadlock


【解决方案1】:

SQL Server 不会提前获取所有锁,即在提出查询计划之后和开始读取数据之前。它只是通过查询计划,按需要的顺序执行其部分,并在需要时获取锁。在您的示例中,它必须首先执行内部查询,以便在稍后尝试获取TableA 上的锁之前获得TableB 上的锁定。

我不确定在这种情况下是否有避免死锁的通用方法。您可以通过在事务顶部放置两个select top 1 * from ... WITH (TABLOCKX)(每个表一个)来最小化它的机会。由于它们会很快被执行,因此介于两者之间的可能性会更小。

【讨论】:

  • 这样工作太糟糕了,谢谢你让我知道。在顶部添加那些虚拟 TABLOCKX 语句不是一个坏主意。那些似乎也可能陷入僵局,但因为它们运行得如此之快,机会更少。
  • 这让我想起了我过去做过的其他事情,我有一个单独的辅助表,其中有一行,在事务的一开始,我将运行一个虚拟更新上锁。任何其他尝试运行相同事务/过程的进程都将被阻止,直到第一个进程提交。在这种情况下,它们是单独的过程,但两个过程可以在帮助表中的单行上运行相同的更新,从而有效地序列化对 TableA 和 TableB 的访问。如果可能的话,我宁愿不要跳过这么多圈,但仍然比死锁要好。
  • 是的,这也是可能的。另一种方法是捕获死锁并重新运行整个事情。重复死锁的可能性要小得多。
【解决方案2】:

减少死锁机会的一种方法是确保查询快速运行。

在更改 sql server 的行为(例如更改锁定模式级别等)之前,我会确保死锁中涉及的查询得到很好的优化。

【讨论】:

  • 好小费,谢谢。我确实发现其中一个查询受益于一个新索引,这是我在发布此问题后添加的。执行时间减少了大约 100-200 毫秒,这仍然是可观的,随着数据量的增加,索引将提供更多帮助。这两个查询每个运行大约 50-200 毫秒,所以不会太破旧。
  • 可以防止死锁的另一件事是让两个查询以相同的顺序访问表 A 和表 B。因此,您可能会考虑调整其他查询以首先访问表 B(与死锁受害者相同)
猜你喜欢
  • 2011-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-04
  • 1970-01-01
  • 2017-10-18
  • 1970-01-01
相关资源
最近更新 更多