为啥选择/读取会导致排他(X)锁?

Posted

技术标签:

【中文标题】为啥选择/读取会导致排他(X)锁?【英文标题】:Why would a select/read cause an exclusive (X) lock?为什么选择/读取会导致排他(X)锁? 【发布时间】:2012-10-03 11:01:34 【问题描述】:

我正在处理的 API 使用的数据库中发生了一些死锁。这仅在 API 进行负载测试时发生。在初步调查和研究中,一些进口指标似乎缺失,现已应用。这似乎解决了死锁问题。在应用索引之前,SQL 正在执行索引扫描。应用索引后,SQL 正在执行索引查找。

提出这个问题的原因是为了巩固我对死锁的理解。我仍然有点困惑,为什么没有索引,选择语句会导致 MS SQL 中的排他 (X) 锁定?

我认为这纯粹是因为我不了解死锁图。从我可以看到下图中的2个进程都在做选择..那怎么会导致排他(X)锁?图表上可能没有什么东西吗?

这是在没有额外索引的情况下发生的死锁图:

...这里是 XML(来自该图:

<deadlock-list>
 <deadlock victim="process4c3708">
  <process-list>
   <process id="process4c3708" taskpriority="0" logused="1580" waitresource="KEY: 5:72057594038910976 (a94bedf44228)" waittime="99" ownerId="13602992" transactionname="user_transaction" lasttranstarted="2012-10-03T10:59:34.830" XDES="0x8cbf23b0" lockMode="S" schedulerid="3" kpid="7588" status="suspended" spid="67" sbid="0" ecid="0" priority="0" trancount="1" lastbatchstarted="2012-10-03T10:59:35.027" lastbatchcompleted="2012-10-03T10:59:35.020" clientapp=".Net SqlClient Data Provider" hostname="DEVMACHINE" hostpid="8440" loginname="user" isolationlevel="read committed (2)" xactid="13602992" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
     <frame procname="adhoc" line="1" stmtstart="170" stmtend="728" sqlhandle="0x02000000b3a88339052734f326b9dbb95deb1d46fe4d192d">
select basefolder1_.Identifier as col_0_0_ from TradeInfo tradepr0_ inner join BaseFolder basefolder1_ on tradepr0_.BaseFolderId=basefolder1_.Id where basefolder1_.BoxId=@p0 and tradepr0_.PersonId=@p1 and tradepr0_.CurrentTrade=1 and tradepr0_.IsActive=1;     </frame>
     <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown     </frame>
    </executionStack>
    <inputbuf>
(@p0 uniqueidentifier,@p1 uniqueidentifier,@p2 uniqueidentifier,@p3 uniqueidentifier)select basefolder1_.Identifier as col_0_0_ from TradeInfo tradepr0_ inner join BaseFolder basefolder1_ on tradepr0_.BaseFolderId=basefolder1_.Id where basefolder1_.BoxId=@p0 and tradepr0_.PersonId=@p1 and tradepr0_.CurrentTrade=1 and tradepr0_.IsActive=1;
select basefolder1_.Identifier as col_0_0_ from TradeInfo tradepr0_ inner join BaseFolder basefolder1_ on tradepr0_.BaseFolderId=basefolder1_.Id where basefolder1_.BoxId=@p2 and tradepr0_.PersonId=@p3 and tradepr0_.SuspendedTrade=1 and tradepr0_.IsSuspended=1;
    </inputbuf>
   </process>
   <process id="process5dd4c8" taskpriority="0" logused="1580" waitresource="KEY: 5:72057594038910976 (b34811986aff)" waittime="301" ownerId="13602927" transactionname="user_transaction" lasttranstarted="2012-10-03T10:59:34.660" XDES="0xafb9f950" lockMode="S" schedulerid="4" kpid="2076" status="suspended" spid="61" sbid="0" ecid="0" priority="0" trancount="1" lastbatchstarted="2012-10-03T10:59:35.020" lastbatchcompleted="2012-10-03T10:59:35.020" clientapp=".Net SqlClient Data Provider" hostname="DEVMACHINE" hostpid="8440" loginname="user" isolationlevel="read committed (2)" xactid="13602927" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
     <frame procname="adhoc" line="1" stmtstart="170" stmtend="728" sqlhandle="0x02000000b3a88339052734f326b9dbb95deb1d46fe4d192d">
select basefolder1_.Identifier as col_0_0_ from TradeInfo tradepr0_ inner join BaseFolder basefolder1_ on tradepr0_.BaseFolderId=basefolder1_.Id where basefolder1_.BoxId=@p0 and tradepr0_.PersonId=@p1 and tradepr0_.CurrentTrade=1 and tradepr0_.IsActive=1;     </frame>
     <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown     </frame>
    </executionStack>
    <inputbuf>
(@p0 uniqueidentifier,@p1 uniqueidentifier,@p2 uniqueidentifier,@p3 uniqueidentifier)select basefolder1_.Identifier as col_0_0_ from TradeInfo tradepr0_ inner join BaseFolder basefolder1_ on tradepr0_.BaseFolderId=basefolder1_.Id where basefolder1_.BoxId=@p0 and tradepr0_.PersonId=@p1 and tradepr0_.CurrentTrade=1 and tradepr0_.IsActive=1;
select basefolder1_.Identifier as col_0_0_ from TradeInfo tradepr0_ inner join BaseFolder basefolder1_ on tradepr0_.BaseFolderId=basefolder1_.Id where basefolder1_.BoxId=@p2 and tradepr0_.PersonId=@p3 and tradepr0_.SuspendedTrade=1 and tradepr0_.IsSuspended=1;
    </inputbuf>
   </process>
  </process-list>
  <resource-list>
   <keylock hobtid="72057594038910976" dbid="5" objectname="ccp.dbo.TradeInfo" indexname="PK__Activity__3214EC07060DEAE8" id="lock8011dd00" mode="X" associatedObjectId="72057594038910976">
    <owner-list>
     <owner id="process5dd4c8" mode="X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process4c3708" mode="S" requestType="wait"/>
    </waiter-list>
   </keylock>
   <keylock hobtid="72057594038910976" dbid="5" objectname="ccp.dbo.TradeInfo" indexname="PK__Activity__3214EC07060DEAE8" id="lock8a864e00" mode="X" associatedObjectId="72057594038910976">
    <owner-list>
     <owner id="process4c3708" mode="X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process5dd4c8" mode="S" requestType="wait"/>
    </waiter-list>
   </keylock>
  </resource-list>
 </deadlock>
</deadlock-list>

【问题讨论】:

除了索引之外,为了避免死锁,确保您的代码以相同的顺序访问表非常重要。 【参考方案1】:

它不会(没有锁定提示)

推测在同一个事务中存在前面的语句(死锁图中未显示)实际获得了X 锁。

注意 logused="1580" 如果语句是独立的 SELECT 也不会发生这种情况

【讨论】:

有趣..我认为一定是这样。找出导致 X 锁定的语句的最佳方法是什么? @CraftyFella - 你能不看调用代码来看看显示的其他语句与哪些语句共享事务吗?否则,可能在负载测试期间运行 Profiler,并查看死锁之前相同 SPID 的先前语句。 谢谢。我想我有足够的时间继续前进..所以我认为正在发生的事情是 2 个 Xclusive 锁正在发生,但添加索引意味着选择不会等待另一个进程中的 Xclusive 锁。因此它可以继续并且不会死锁。 您还可以使用过滤的扩展事件来触发捕获语句文本的感兴趣对象上的X 锁定(只是一个我没有研究过的想法详细) 你如何看待我关于索引为什么能解决它的理论?【参考方案2】:

死锁 XML 中的任何 SELECT 都没有实际获得 X 锁。死锁涉及的两个资源目前在 X 模式下拥有,在 S 模式下请求。这意味着每个事务先前锁定了资源(在本例中为键),也许它运行了更新/插入该行的 DML 语句。 SELECT 语句只想读取行,因此它们请求 S 模式。

【讨论】:

有趣..我认为一定是这样。找出导致 X 锁定的语句的最佳方法是什么? 找出导致 X 锁的语句的最佳方法是什么:源代码检查。 想知道是否有办法在 Profiler 中做到这一点。打开一些设置。“在死锁图中显示进程的所有语句”哦,好吧 该语句可能在一小时前执行(不太可能)。关键是这是过去的事情,保留执行历史以防发生死锁是不现实的。您当然可以使用 Profiler 查看正在执行的内容,请参阅 SP:StmtCompletedSQL:StmtCompleted

以上是关于为啥选择/读取会导致排他(X)锁?的主要内容,如果未能解决你的问题,请参考以下文章

如何解决高并发秒杀的超卖问题

在 Linux 中对只读文件设置排他锁

sql server 锁

抢购高并发问题

为啥在可重复读取中会发生写入偏斜?

mysql锁机制