有理由使用SELECT…WITH XLOCK?


11

我面临着一些反复出现的死锁,其中之一是键锁,并且包含带有XLOCK提示的SELECT查询,该查询成为了死锁的受害者。另一个语句是对其中一个表的INSERT,该表是第一个查询的视图的一部分。

视图:

create view dbo.viewE
 as
    select * from dbo.E  
    where myValue > 13000 

选择查询:

select * from dbo.viewE with (XLOCK) where A > GETUTCDATE() 

INSERT语句:

INSERT INTO [dbo].[E] (myValue,A) VALUES (10,GetDate())

基础表dbo.E在大约20列中拥有约300万行,其中有些是ntext。

取出查询并使用两个事务手动进行模拟,该行为是可重现的。如果从选择中删除了XLOCK,则行为会更改。

死锁图:

<deadlock-list>
 <deadlock victim="process222222221">
  <process-list>
   <process id="process222222221" taskpriority="0" logused="0" waitresource="KEY: 5:72057604035644444 (ccdf51accc0c)" waittime="2522" ownerId="27202256401" transactionname="SELECT" lasttranstarted="2015-09-14T16:32:36.160" XDES="0x2f1ec5ca0" lockMode="RangeX-X" schedulerid="15" kpid="12936" status="suspended" spid="359" sbid="0" ecid="0" priority="0" trancount="0" lastbatchstarted="2015-09-14T16:32:36.160" lastbatchcompleted="2015-09-14T16:32:36.160" clientapp="x" hostname="x" hostpid="14536" loginname="x" isolationlevel="serializable (4)" xactid="27202256401" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
     <frame procname="adhoc" line="1" stmtstart="48" sqlhandle="0x02000000611e4523142b2318c47c87313a9b2ba587ff3130">
        SELECT * FROM viewE WITH (XLOCK) WHERE A &lt; GetUtcDate()      </frame>
     <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown     </frame>
    </executionStack>
    <inputbuf>
(@UICulture nvarchar(5))SELECT * FROM viewE WITH (XLOCK) WHERE A &lt; GetUtcDate()    </inputbuf>
   </process>
   <process id="process6022222" taskpriority="0" logused="161152" waitresource="KEY: 5:72057604035644444 (cd874c2ba438)" waittime="1370" ownerId="27202248438" transactionguid="0x8de5ccd6eeef67469c6234af59e44ca5" transactionname="DTCXact" lasttranstarted="2015-09-14T16:32:34.767" XDES="0x4aa0bf950" lockMode="RangeI-N" schedulerid="14" kpid="6636" status="suspended" spid="329" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2015-09-14T16:32:37.300" lastbatchcompleted="2015-09-14T16:32:37.300" clientapp="x" hostname="x" hostpid="14536" loginname="x" isolationlevel="read uncommitted (1)" xactid="27202248438" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
     <frame procname="adhoc" line="1" stmtstart="936" sqlhandle="0x020000004853462f09790a4ddedc0d574c2afa539aef1c0e">
     INSERT INTO [E] ([a], [b], [c],...) VALUES (@aDate, @bDate, @c, ...)
     </frame>
     <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown     </frame>
    </executionStack>
    <inputbuf>INSERT INTO [E] ([a], [b], [c],...) VALUES (@aDate, @bDate, @c, ...)
    </inputbuf>
   </process>
  </process-list>
  <resource-list>
   <keylock hobtid="72057604035644444" dbid="5" objectname="db.dbo.E" indexname="IX_index1" id="lock258b6dc80" mode="X" associatedObjectId="72057604035644444">
    <owner-list>
     <owner id="process6022222" mode="X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process222222221" mode="RangeX-X" requestType="wait"/>
    </waiter-list>
   </keylock>
   <keylock hobtid="72057604035644444" dbid="5" objectname="db.dbo.E" indexname="IX_index1" id="lock7b145c400" mode="RangeX-X" associatedObjectId="72057604035644444">
    <owner-list>
     <owner id="process222222221" mode="RangeX-X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process6022222" mode="RangeI-N" requestType="wait"/>
    </waiter-list>
   </keylock>
  </resource-list>
 </deadlock>
</deadlock-list>

据我了解,我正在查看KEYLOCK死锁,它基本上是由未发现的索引查询引起的,该查询使用非聚集索引和聚集索引来收集所需的值,对吗?

我的问题:

  1. 由于涉及必需的NTEXT列,因此无法创建覆盖索引。大大减少行数在这里有帮助吗?
  2. 有什么好理由我只是不知道SELECT是用XLOCK执行的?没有XLOCK也会发生死锁吗?

Answers:


15

据我了解,我正在查看KEYLOCK死锁,它基本上是由未发现的索引查询引起的,该查询使用非聚集索引和聚集索引来收集所需的值,对吗?

本质上是。读取操作(选择)首先访问非聚集索引,然后访问聚集索引(查找)。写操作(插入)首先访问聚集索引,然后访问非聚集索引。以不同的顺序访问拥有不兼容锁的相同资源可能导致死锁。

大大减少行数在这里有帮助吗?

可能会,因为较少的资源被锁定,该操作将趋向于更快地完成。如果确实有帮助,它可以减少死锁,但很可能无法消除死锁(请继续阅读)。

有什么好理由我只是不知道SELECT是用XLOCK执行的?

并不是的。这样的锁定提示通常是在人们不了解隔离,锁定和死锁如何工作的情况下引入的,他们拼命地尝试减少或消除问题。

没有XLOCK也会发生死锁吗?

,如果选择实际上以读取未提交的隔离状态运行,因为不兼容的锁不会以不同的顺序获取(并保持)。

,如果使用锁定隔离级别,并且以不一致的顺序获取和保持不兼容的锁,例如在非集群上共享(S),则在读取时在集群上共享S。在这种情况下,死锁发生的可能性取决于所获取的锁定数量以及锁定的时间。

忠告

真正脱颖而出(经审查)的是select事务在可序列化隔离下运行。这可能是由您的框架设置的,或者是由于使用了DTC(分布式事务处理协调器)-请参阅死锁图中的transactionname =“ DTCXact”。您应该调查造成这种情况的原因,并在可能的情况下进行更改。

如果不升级为可序列化的,则很有可能在XLOCK提示被删除的情况下不会发生此死锁。就是说,您将在读未提交隔离下阅读,这种隔离几乎没有一致性保证。

如果您的应用程序和SQL Server的代码可以容忍读取行版本,更改为读提交快照隔离(RCSI)或快照隔离(SI)的读取也将避免死锁(XLOCK删除!),而呈现出一致的,点式提交数据的实时视图。当然,这也假定您可以避免可序列化的隔离。

最终,XLOCK提示适得其反,但是您确实需要研究使用可序列化隔离级别的原因。这trancount = 2也很有趣-也许您是在无意中嵌套事务。还有其他检查。


2
  1. 大大减少行数将减少出现死锁的可能性,但这种情况不会完全消失。

简单来说,select首先使用索引来确定要选择的行,然后在插入插入行的同时获取行,然后尝试更新(XLOCKED)索引。

  1. 如果应用程序开发人员希望在同一事务中稍后对数据进行更新,则倾向于使用XLOCK。这样可以确保没有人可以更新其下的数据。我将调查应用程序在做什么,以查看是否需要XLOCK。

话虽如此,删除XLOCK可能无法解决问题。SELECT仍将在索引上取得共享锁,而INSERT将需要XLOCK对其进行更新。共享锁和XLOCK不能同时存在于对象上,因此您仍然会遇到死锁。IX_Index1必须为MyValue或A,或两者都为。

此类死锁通常是由于索引设计不良和/或索引太多而发生的。或写得不好的代码。最好的选择是查看是否可以通过某种方式重写选择以使用另一个索引。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.