SQL Select 更新死锁

SQL Deadlock on Select Update

我正在尝试解决 SQL 死锁问题。下面是一份system_health报告

<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>

据此正确的是:

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

所以我的问题是,有哪些选项可以解决此问题?我知道我可以在 Select 语句中添加一个指令来表示 'dirty data is ok' 但是感觉不对...

从广义上讲,当两个事务锁定了 table 并且都无法继续时,就会发生这种死锁,因为每个事务都锁定了另一个事务所需的 table。

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

在没有看到查询的情况下,您的广泛选择是

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

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

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

另一方面,更新首先更新基础table(聚簇索引),然后导致所有其他引用正在更新的列的索引在之后更新。

如您所见,索引的访问顺序是相反的,并且因为两个语句恰好同时发生,所以它们死锁了。

select * from table where fk = @Id

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

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