Azure 上同一个 table 中更新的死锁与页面锁定

Deadlock on update in the same table on Azure with pagelock

我有一个 table 显示 Azure DB 上的事件。事件来得很频繁。他们在添加,然后在更新。

更新查询如下所示:

SET [DeactivationEventId] = @EventId, [Deactivated] = 1, [Type] = @DeactivationType
WHERE [DeactivationEventId] IS NULL
AND [ItemId] = @ItemId
AND [Id] <> @EventId

图形如下所示:

  <victim-list>
    <victimProcess id="process1752f0bbc28" />
  </victim-list>
  <process-list>
    <process id="process1752f0bbc28" taskpriority="0" logused="888" waitresource="PAGE: 6:1:667 " waittime="4144" ownerId="7884216" transactionname="user_transaction" lasttranstarted="2020-12-14T01:39:04.073" XDES="0x1753ad74428" lockMode="S" schedulerid="2" kpid="64600" status="suspended" spid="83" sbid="2" ecid="0" priority="0" trancount="2" lastbatchstarted="2020-12-14T01:39:04.073" lastbatchcompleted="2020-12-14T01:39:04.073" lastattention="1900-01-01T00:00:00.073" clientapp="Core .Net SqlClient Data Provider" hostname="RD00155D55DC34" hostpid="8768" loginname="westbrookb" isolationlevel="read uncommitted (1)" xactid="7884216" currentdb="6" currentdbname="Rythmos" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
      <executionStack>
        <frame procname="unknown" queryhash="0xb5d19baa56c298af" queryplanhash="0xa0741009000fbf64" line="1" stmtstart="102" stmtend="558" sqlhandle="0x020000007676e21a17f941df30c67e9f52cb3adb4dbf4e800000000000000000000000000000000000000000">
unknown    </frame>
        <frame procname="unknown" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
unknown    </frame>
      </executionStack>
      <inputbuf>
(@EventId int,@DeactivationType int,@ItemIdbigint)UPDATE [dbo].[Events] WITH (ROWLOCK)
SET [DeactivationEventId] = @EventId, [Deactivated] = 1, [Type] = @DeactivationType
WHERE [DeactivationEventId] IS NULL
AND [ItemId] = @ItemId
AND [Id] <> @EventId</inputbuf>
    </process>
    <process id="process17537256ca8" taskpriority="0" logused="1528" waitresource="PAGE: 6:1:667 " waittime="4141" ownerId="7884219" transactionname="user_transaction" lasttranstarted="2020-12-14T01:39:04.083" XDES="0x17541f1c428" lockMode="S" schedulerid="3" kpid="45516" status="suspended" spid="85" sbid="2" ecid="0" priority="0" trancount="2" lastbatchstarted="2020-12-14T01:39:04.087" lastbatchcompleted="2020-12-14T01:39:04.080" lastattention="1900-01-01T00:00:00.080" clientapp="Core .Net SqlClient Data Provider" hostname="RD0003FF811932" hostpid="8012" loginname="westbrookb" isolationlevel="read uncommitted (1)" xactid="7884219" currentdb="6" currentdbname="Rythmos" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
      <executionStack>
        <frame procname="unknown" queryhash="0xb5d19baa56c298af" queryplanhash="0xa0741009000fbf64" line="1" stmtstart="102" stmtend="558" sqlhandle="0x020000007676e21a17f941df30c67e9f52cb3adb4dbf4e800000000000000000000000000000000000000000">
unknown    </frame>
        <frame procname="unknown" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
unknown    </frame>
      </executionStack>
      <inputbuf>
(@EventId int,@DeactivationType int,@ItemIdbigint)UPDATE [dbo].[Events] WITH (ROWLOCK)
SET [DeactivationEventId] = @EventId, [Deactivated] = 1, [Type] = @DeactivationType
WHERE [DeactivationEventId] IS NULL
AND [ItemId] = @ItemId
AND [Id] <> @EventId</inputbuf>
    </process>
  </process-list>
  <resource-list>
    <**pagelock** fileid="1" pageid="667" dbid="6" subresource="FULL" objectname="065e5285-8d87-4534-93bb-20d8770ae630.dbo.WirepasDeviceEvents" id="lock174acf59300" mode="IX" associatedObjectId="72057594085638144">
      <owner-list>
        <owner id="process17537256ca8" mode="IX" />
        <owner id="process17537256ca8" mode="S" requestType="convert" />
      </owner-list>
      <waiter-list>
        <waiter id="process1752f0bbc28" mode="S" requestType="convert" />
      </waiter-list>
    </pagelock>
    <**pagelock** fileid="1" pageid="667" dbid="6" subresource="FULL" objectname="065e5285-8d87-4534-93bb-20d8770ae630.dbo.WirepasDeviceEvents" id="lock174acf59300" mode="IX" associatedObjectId="72057594085638144">
      <owner-list>
        <owner id="process1752f0bbc28" mode="IX" />
        <owner id="process1752f0bbc28" mode="S" requestType="convert" />
      </owner-list>
      <waiter-list>
        <waiter id="process17537256ca8" mode="S" requestType="convert" />
      </waiter-list>
    </pagelock>
  </resource-list>
</deadlock>

我在此 table 上有四个非聚集索引。 这是为了避免这种僵局:

(
    [DeactivationEventId] ASC,
    [ItemId] ASC,
    [Id] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, DROP_EXISTING = OFF, ONLINE = OFF) ON [PRIMARY]
GO

有很多警报在毫秒内插入然后更新: **2020-12-14 01:38:00.180

2020-12-14 01:38:00.203**

我的问题。如果我用两个查询分开它:

select Id 
from [dbo].[Events] WITH (NoLock)
WHERE [DeactivationEventId] IS NULL
AND [ItemId] = @ItemId
AND [Id] <> @EventId
SET [DeactivationEventId] = @EventId, [Deactivated] = 1, [Type] = @DeactivationType
WHERE Id = @Id

避免页面锁定。 这是执行计划:

你怎么看?

已编辑。

分离请求目前确实有帮助。我现在没有陷入僵局。

在图中,您可以看到死锁图的两方是相同的查询计划(具有相同的散列,因此很可能是相同的计划)。您的重写正在寻找您要更新的特定行,然后执行“广泛”更新计划来更新每个索引。它将锁定每个 b-tree/heap/etc,因为它触及每一行计划中的内容。我假设您正在使用自动提交交易,因为您没有提及任何内容。

您碰巧通过将 select 部分(可能会扫描很多行,但我无法确定计划)移出采用更新需要 U 锁。 (S 锁与其他 S 锁兼容,但与 U 锁不兼容,因此在该查询的另一个实例具有 S 锁时尝试获取 U 锁的并发查询可能会导致死锁)。

避免死锁的其他方法包括在原始的、较大的更新查询上添加 UPDLOCK 提示。对于读取的每一行,这将采用 U 锁而不是 S 锁(即使它不会被更新)。您选择的模式可能非常适合您的应用程序,但如果您在单行情况下同时执行同一查询(或针对每个尝试执行单例点查找的相同 table 的类似查询)出现死锁, 在某些情况下,不同的查询计划可以以不同的顺序为同一 table 中的索引获取锁。例如,如果查询 1 在索引 1 上锁定,然后在聚集索引上锁定,查询 2 在聚集索引上锁定,然后在索引 1 上锁定,这些也可能会死锁。可以使用 UPDLOCK 方法修复此模式。