【问题标题】:SQL Deadlock on Select Update选择更新时的 SQL 死锁
【发布时间】:2020-12-04 09:04:54
【问题描述】:

我正在尝试解决 SQL 死锁问题。以下是系统健康报告

<deadlock>
  <victim-list>
    <victimProcess id="process87d03ccf8" />
  </victim-list>
  <process-list>
    <process id="process87d03ccf8" taskpriority="0" logused="0" waitresource="KEY: 7:72057901332627456 (f323ae9efc53)" waittime="1087" ownerId="20788909869" transactionname="SELECT" lasttranstarted="2020-12-03T23:13:56.500" XDES="0x338706d10" lockMode="S" schedulerid="6" kpid="38240" status="suspended" spid="103" sbid="0" ecid="0" priority="0" trancount="0" lastbatchstarted="2020-12-03T23:13:56.490" lastbatchcompleted="2020-12-03T23:13:56.490" lastattention="1900-01-01T00:00:00.490" clientapp=".Net SqlClient Data Provider" hostname="ID45846" hostpid="58020" loginname="ubuser" isolationlevel="read committed (2)" xactid="20788909869" currentdb="7" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
      <executionStack>
        <frame procname="x.dbo.OrderData_GetByOrderID" line="6" stmtstart="124" sqlhandle="0x03000700cddb7412e6afdc00a0a9000001000000000000000000000000000000000000000000000000000000"></frame>
      </executionStack>
      <inputbuf>
Proc [Database Id = 7 Object Id = 309648333]   </inputbuf>
    </process>
    <process id="process32f127868" taskpriority="0" logused="112" waitresource="KEY: 7:72057901332692992 (004616e83cc3)" waittime="1087" ownerId="20788909868" transactionname="UPDATE" lasttranstarted="2020-12-03T23:13:56.500" XDES="0x81bad63a8" lockMode="X" schedulerid="15" kpid="66292" status="suspended" spid="61" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2020-12-03T23:13:56.490" lastbatchcompleted="2020-12-03T23:13:56.490" lastattention="1900-01-01T00:00:00.490" clientapp=".Net SqlClient Data Provider" hostname="ID45846" hostpid="58020" loginname="ubuser" isolationlevel="read committed (2)" xactid="20788909868" currentdb="7" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
      <executionStack>
        <frame procname="x.dbo.OrderData_Set" line="36" stmtstart="1662" sqlhandle="0x030007002608c15b50af010156ac000001000000000000000000000000000000000000000000000000000000"></frame>
      </executionStack>
      <inputbuf>
Proc [Database Id = 7 Object Id = 1539377190]   </inputbuf>
    </process>
  </process-list>
  <resource-list>
    <keylock hobtid="72057901332627456" dbid="7" objectname="unidbmaster.dbo.OrderData" indexname="PK_OrderData" id="lock494546200" mode="X" associatedObjectId="72057901332627456">
      <owner-list>
        <owner id="process32f127868" mode="X" />
      </owner-list>
      <waiter-list>
        <waiter id="process87d03ccf8" mode="S" requestType="wait" />
      </waiter-list>
    </keylock>
    <keylock hobtid="72057901332692992" dbid="7" objectname="unidbmaster.dbo.OrderData" indexname="IX_OrderData_Currency_ParcelData_GrandTotal_CustomsValue" id="lock73ecc1b80" mode="S" associatedObjectId="72057901332692992">
      <owner-list>
        <owner id="process87d03ccf8" mode="S" />
      </owner-list>
      <waiter-list>
        <waiter id="process32f127868" mode="X" requestType="wait" />
      </waiter-list>
    </keylock>
  </resource-list>
</deadlock>

基于此正确的是:

  • Update 进程锁定了 PK_OrderData,而 Select 进程想要它?
  • Select 进程锁定了 IX_OrderData_Currency_ParcelData_GrandTotal_CustomsValue 并且 Update 进程想要它?
  • Select 进程正在选择 Update 进程要更新的同一行

我假设解决此问题的一种方法是删除 IX_OrderData_Currency_ParcelData_GrandTotal_CustomsValue 索引,但它在其他地方使用。

所以我的问题是,有什么方法可以解决这个问题?我知道我可以在 Select 语句中添加一个指令,说“脏数据没问题”,但是感觉不对……

【问题讨论】:

  • 更新是什么样的?
  • RCSI 开启了吗?如果没有,那可能是一个解决方案....
  • @Mitch - 但需要注意 RCSI 也可能有自己的问题。
  • @seanb:是的,这是真的。但这不是咨询演出,所以只是对 OP 将进一步调查的假设提出建议。

标签: sql sql-server database-deadlocks


【解决方案1】:

选择和更新之间发生死锁是因为选择使用首先找到记录最快的索引,然后使用基表(聚集索引)检索其余信息,因为您正在选择所有列.

另一方面,更新是首先更新基表(聚集索引),然后会导致引用正在更新的列的所有其他索引在此之后更新。

如您所见,索引的访问顺序是颠倒的,因为这两个语句发生在完全相同的时间,所以它们死锁了。

select * from table where fk = @Id

一种可能的解决方案是外键列上的另一个索引。假设此列不是更新的一部分,则不会受到更新的影响。

另一种可能的解决方案是将您选择的列限制为您需要的列(select * 几乎总是一个坏主意)并为您选择的所有列以及外键创建一个覆盖索引。这样选择只会命中一个索引。

【讨论】:

  • 感谢您的回答!我所做的是获得两个查询的执行计划。这表明两者都使用相同的索引。然后我在 FK 上创建了一个新索引,但这并没有改变执行计划,直到我将剩余的列包含在索引中。到目前为止,我还没有看到系统健康数据中的死锁。它通常每天记录 8 次,所以如果我有几天没有任何问题,我会认为它已解决
【解决方案2】:

一般来说,当两个事务都锁定了表并且由于每个事务都锁定了另一个事务需要的表而导致两者都无法继续时,就会发生这样的死锁。

我认为 SELECT 实际上是事务的一部分(而不仅仅是一个独立的查询)。如果它只是一个独立的查询,并且 UPDATE 事务已经开始,那么 SELECT 查询将一直等到 UPDATE 完成。但是,与 SELECT 相同的事务中的某些东西正在锁定 UPDATE 然后需要的东西。

没有看到查询,您的广泛选择是

  • 问问自己是否需要交易?是否可以将部分或全部语句从事务中移出?
  • 对于两个事务,UPDATE 表以相同的顺序(例如,两个事务首先更新表 A,然后更新表 B - 而不是一个先执行 A 然后 B,另一个执行 B 然后 A)。
    • ...如果可能,每个表只更新一次

Brent Ozar 有一个 great video on deadlocks - 我强烈推荐它 - 尽管它将近一个小时,但它是一个很好的演示以及如何解决它。确实,我在这里的回答几乎都是基于他的视频。

【讨论】:

  • 感谢您的回复。这可能是不好的做法,但是数据库或代码中没有使用任何事务。调用 SP 时,这是来自调用应用程序的 2 个独立连接。 select 语句实际上是一行 select * from table where fk = id。 fk 是外键。
  • 好的。没有交易并不是特别糟糕的做法 - imo 人们比他们应该更频繁地使用它们。关于这个问题——这很奇怪,超出了我所经历的范围;但是,@DaleK 的答案与我在研究时看到的另一个答案相似——他的回答很可能会带你朝着正确的方向前进。我会在此处保留此答案,以表明它不是答案。
  • 我看过 Brent Ozar 的一些视频 - 他的风格很有趣,但内容却很棒。感谢您的帮助!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-28
  • 1970-01-01
  • 2014-01-14
  • 2017-10-05
相关资源
最近更新 更多