SQL Server 死锁在一张表上的两次选择和一次更新之间

fin*_*nal 5 sql-server deadlock deadlock-graph

我们的应用程序时不时地(大约每周一次)遇到死锁。

罪魁祸首似乎是带有两个选择的查询。其中之一是出于性能原因填充临时表,另一个是具有许多连接的相对复杂的选择,以返回具有许多详细信息的所有约会的列表。我看到的关于第二个选择的唯一可能特别的地方是它包含一个自连接。二选查询始终是 SQL Server 死锁事件报告的一部分。

另一个查询是对同一个表的简单 DML 查询(插入或更新),尽管这并不总是相同的 DML 查询。这两个查询都以标准READ COMMITTED隔离方式运行,而不是在显式事务中运行。

这两个查询大致如下(我已缩短它们以进行澄清)

DECLARE @futureAppointments TABLE(clientId int, StartDate date)
INSERT INTO @futureAppointments SELECT clientId, StartDate FROM Appointments where StartDate >= @startDate

SELECT *, (SELECT COUNT(*) FROM @futureAppointments fa WHERE fa.clientId = a.clientId AND fa.StartDate > a.StartDate)
FROM Appointments a
join b on a.fk_b = b.id
join c on a.fk_c = c.id
join Appointments d on c.somefield = d.anotherfield
WHERE a.StartDate >= @startDate AND a.StartDate <= @endDate
Run Code Online (Sandbox Code Playgroud)
UPDATE Appointments SET someField = @value WHERE id = @id
Run Code Online (Sandbox Code Playgroud)

示例 2:deadlock2.xmlExmaple2 的死锁图 示例 3:deadlock3.xmlExample3 的死锁图

在这种情况下,我将如何尝试防止发生死锁?另外,有谁知道为什么第一个带有两个选择的语句会像示例 3 一样在所选表的 PK 上获得 U 锁?我不认为这很重要,但这似乎很奇怪。

Ran*_*gen 7

查看死锁图时:

在此处输入图片说明

更新查询 - SPID 75

更新查询锁定(spid 75)非常简单,在键/行值上请求X(独占)锁,而该行当前被选择查询锁定。

更新查询还持有一个页面上的IX(意图排他)锁,正如在属于该页面的行上获取排他锁时所预期的那样。的IX锁与兼容IS(意向共享)由选择查询发出锁(作为结果小号上的行锁)。

请参阅:锁兼容性矩阵


选择查询 - SPID 103

The unusual part of your deadlock is that the select query (spid 103) wants a S (shared) lock on both the row & the page that has the row data (and possibly other row data). Especially since lock escalation from row to page is not possible (row to table & page to table). A previous transaction holding the lock is also ruled out.

The explanation seems to be in the double access to the dbo.Appointments table. Locks are taken twice and one of these table accesses will want the page lock, while the other one already aquired the rowlock.

The update fires in between these shared locks being aquired.

An example of locks requested / acquired in order

  1. A S Lock on the key/row is taken by the first read access on the dbo.Appointments table as part of the select query
  2. An IX Lock is taken on the page that has the row data by the update query
  3. X lock is requested on the row by the update query
  4. The S lock on the page that also has the row data is requested by the second access to dbo.Appointments as part of the select query

All this means that this part of the select query is your deadlock problem:

SELECT *, (SELECT COUNT(*) FROM @futureAppointments fa WHERE fa.clientId = a.clientId AND fa.StartDate > a.StartDate)
FROM Appointments a
join b on a.fk_b = b.id
join c on a.fk_c = c.id
join Appointments d on c.somefield = d.anotherfield
WHERE a.StartDate >= @startDate AND a.StartDate <= @endDate;
Run Code Online (Sandbox Code Playgroud)

How would I try to prevent deadlocks from happening in this scenario?

Reduce

To reduce the probability of the deadlock happening you could look into optimizing the select query by rewriting or adding indexes.

Remove

To remove the possibility of deadlocks between these two queries entirely you could split the self join in two parts by using a temp table, or take a shared page or table lock on the Appointments table by using WTIH(PAGLOCK) / WITH(TABLOCK). Remember that this impacts concurrency.

另外,有谁知道为什么第一个带有两个选择的语句会像示例 3 一样在所选表的 PK 上获得 U 锁?我不认为这很重要,但这似乎很奇怪。

所有者(选择查询)持有密钥上的共享锁。

一个Ù(更新)锁(从更新查询推出)是与共享锁见兼容:锁兼容性矩阵

更新查询尝试将 U 锁转换为排它锁:

<waiter id="process24b7826bc28" mode="X" requestType="convert" />.

使用SentryOne Plan Explorer(免费工具)打开 XML 时,信息是正确的。