了解特定类型的死锁

Posted

技术标签:

【中文标题】了解特定类型的死锁【英文标题】:Understanding a specific type of deadlock 【发布时间】:2017-02-14 10:27:59 【问题描述】:

我试图是如何产生的。

我有一个非常简单的死锁图,其中包含两个资源和两个进程。一个运行SELECT,另一个运行UPDATE 语句。我知道在某些情况下这会导致死锁,但我不明白在这种特定情况下它是如何发生的。

受害者的询问:

SELECT id_particle, id_event, dt_created, dt_rcvd, tlx_no, from_routename, re, deleted 
FROM msg_list 
WHERE to_routename =  @P0  
AND ((dt_answ IS NULL AND b_complete = 0 AND id_event=6) OR (id_event = 10 AND deleted = 0) 
OR (from_id_post = 9705 AND deleted = 0)) 
ORDER BY dt_created ASC

获胜者的查询:

UPDATE msg_list 
SET
  ID_EVENT=7, 
  STATUS='Answered', 
  DT_ANSW=ts '2017-02-12 05:34:14'
WHERE ID_PARTICLE = 46211816

完整的死锁图:

<deadlock-list>
 <deadlock victim="process30aa42d468">
  <process-list>
   <process id="process30aa42d468" taskpriority="0" logused="0" waitresource="PAGE: 6:1:155679 " waittime="921" ownerId="427175775" transactionname="SELECT" lasttranstarted="2017-02-12T05:34:48.293" XDES="0x3126fbba40" lockMode="S" schedulerid="4" kpid="10648" status="suspended" spid="184" sbid="0" ecid="0" priority="0" trancount="0" lastbatchstarted="2017-02-12T05:34:48.293" lastbatchcompleted="2017-02-12T05:34:48.280" lastattention="1900-01-01T00:00:00.280" clientapp="jTDS" hostname="LNS" hostpid="123" loginname="MsgStore" isolationlevel="read committed (2)" xactid="427175775" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
     <frame procname="tempdb.dbo.#jtds000003_____________________________________________________________________________________________________________0042FD9D" line="1" stmtstart="92" stmtend="668" sqlhandle="0x030002005819e1b4efce580018a7000001000000000000000000000000000000000000000000000000000000">
SELECT id_particle, id_event, dt_created, dt_rcvd, tlx_no, from_routename, re, deleted FROM msg_list WHERE to_routename =  @P0  AND ((dt_answ IS NULL AND b_complete = 0 AND id_event=6) OR (id_event = 10 AND deleted = 0) OR (from_id_post = 9705 AND deleted = 0)) ORDER BY dt_created AS     </frame>
    </executionStack>
    <inputbuf>
Proc [Database Id = 2 Object Id = -1260316328]    </inputbuf>
   </process>
   <process id="process319282aca8" taskpriority="0" logused="21256" waitresource="PAGE: 6:1:396658 " waittime="869" ownerId="427175573" transactionname="implicit_transaction" lasttranstarted="2017-02-12T05:34:48.210" XDES="0x30c2dbc408" lockMode="IX" schedulerid="4" kpid="11980" status="suspended" spid="99" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2017-02-12T05:34:48.430" lastbatchcompleted="2017-02-12T05:34:48.427" lastattention="2017-02-12T03:34:28.130" clientapp="NotesMover" hostname="LNS" hostpid="8828" loginname="Notesmover" isolationlevel="read committed (2)" xactid="427175573" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128058">
    <executionStack>
     <frame procname="adhoc" line="1" stmtstart="364" stmtend="812" sqlhandle="0x020000006d523b316d15563a50b97ce8d56da3cf6d8fc4450000000000000000000000000000000000000000">
unknown     </frame>
     <frame procname="adhoc" line="1" stmtend="554" sqlhandle="0x02000000d8b78713c51b1588947edf24f2d7b69031f4f1d60000000000000000000000000000000000000000">
unknown     </frame>
     <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
unknown     </frame>
    </executionStack>
    <inputbuf>
UPDATE msg_list SET ID_EVENT=7, STATUS='Answered', DT_ANSW=ts '2017-02-12 05:34:14'WHERE ID_PARTICLE = 46211816    
    </inputbuf> 
   </process>
  </process-list>
  <resource-list>
   <pagelock fileid="1" pageid="155679" dbid="6" subresource="FULL" objectname="MsgStore.dbo.msg_list" id="lock31683f2400" mode="IX" associatedObjectId="72057594523811840">
    <owner-list>
     <owner id="process319282aca8" mode="IX"/>
    </owner-list>
    <waiter-list>
     <waiter id="process30aa42d468" mode="S" requestType="wait"/>
    </waiter-list>
   </pagelock>
   <pagelock fileid="1" pageid="396658" dbid="6" subresource="FULL" objectname="MsgStore.dbo.msg_list" id="lock2f80c2e300" mode="S" associatedObjectId="72057594523811840">
    <owner-list>
     <owner id="process30aa42d468" mode="S"/>
    </owner-list>
    <waiter-list>
     <waiter id="process319282aca8" mode="IX" requestType="wait"/>
    </waiter-list>
   </pagelock>
  </resource-list>
 </deadlock>
</deadlock-list>

以及相同的图形表示:

以下是运行这两个查询时生成的查询计划:

受害者:

获胜者:

受害者使用的ncci_rcvd_list 索引是一个非聚集覆盖索引,具有单个键列和多个包含列。其中包含的列之一由获胜者更新。键列未更新。

mgs_list 表有大约 500,000 条记录。 SELECT 查询返回的结果集通常只有几行,比如十几行(尽管偶尔可能会大到几千)。

有人能解释一下这种情况是如何导致死锁的吗?

这是从经常发生这种情况的生产系统中捕获的。我已经通过将事务隔离级别更改为读取提交的快照解决了这个问题,这不再是问题,但我想了解那里发生了什么,然后看看是否有不同的解决方案。

【问题讨论】:

能否将执行计划粘贴为xml并分享 【参考方案1】:

页面 155679 在 IX 模式下归 process319282aca8(UPDATE)所有,在 S 模式下归 process30aa42d468(SELECT)想要。页面 396658 在 S 模式下由 SELECT 拥有,在 IX 模式下由 UPDATE 想要。

问题在于两个查询都缺少索引。 UPDATE 使用索引但没有覆盖,因此它需要在聚集索引中查找 RID。 SELECT 是一次扫描(由页级 S 锁给出......)。这保证了并发下的死锁。

这是另一个滥用表格作为队列的例子。阅读Using tables as Queues。将您的状态与您的事件分开。仅对事件进行入队和出队。

PS。 “受害者”将始终是回滚工作较少的事务,并且在读取与写入中,这将始终是读取。另外,我怀疑您发布的 SELECT 执行计划实际上是死锁的。

【讨论】:

顺便说一句,你在那个表上有任何 UPDATE 触发器吗? 是的,该表上确实有一个简单的更新触发器。它更新另一个数据库的统计表中的记录。否则阅读链接的文档......除非它包含在该文档中,否则您能否解释为什么您认为选择是扫描并且计划是“错误的”?毕竟选择语句有一个覆盖索引,这是我每次手动运行该查询时得到的计划。 @RemusRusanu:你怎么确定的,桌子上有一个更新触发器 @TheGameiswar 来自执行堆栈帧。 why you think that the select is a scan 页级 S 锁通常是由扫描引起的。不过,确实可以在ncci_rcv_list 上(我没有在资源上看到indexname,但我认为页面锁不会发布所有者索引名称,只有对象)。如果是ncci_rcv_list,它必须覆盖由UPDATE 更新的dt_answ,说明NCCI 上的死锁。

以上是关于了解特定类型的死锁的主要内容,如果未能解决你的问题,请参考以下文章

了解golang频道:死锁

当你碰到了MySQL中的死锁,你了解这些机制吗?

深入了解Java并发——《Java Concurrency in Practice》10.避免活跃性危险

并发编程之死锁解析

多线程之死锁就是这么简单

[锁] 线程死锁解析