我的 MS SQL Server 2019 数据库中面临源自两个删除语句的死锁。我们的隔离级别是 READ_COMMITTED,但未打开 READ_COMMITTED_SNAPSHOT。
我的猜测是,发生死锁是因为 ID 非常接近,因此驻留在同一页面中。但根据死锁图,两个进程ID访问不同的页面。
为什么会发生这种死锁以及如何解决它?
我的一个想法是强制执行删除的系统为每个删除使用相同的连接/会话,这将导致它们按顺序执行而不是同时执行。但这会增加我们的运行时间。
根据要求,我在下面分享了所涉及的查询、完整的表和索引定义、死锁 XDL/XML 图作为文本,并请通过 brentozar.com/pastetheplan 分享查询计划。
出于安全原因,我已将表名称替换为“Our_Table”,并将过滤后的列名称替换为“Foreign_Key_Column”。在创建表语句中,我以类似的方式替换列和键名称,但尝试保持有效性。
注意:服务器语言是德语。
第一:涉及的查询
Delete from Our_Table where Foreign_Key_Column = 78905 --Process 51
Delete from Our_Table where Foreign_Key_Column = 78906 --Process 68
第二:完整的表和索引定义
USE [Our_Database]
GO
/****** Object: Table [dbo].[Our_Table] Script Date: 06.02.2024 16:17:28 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Our_Table](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Other_Foreign_Key_Column_1] [int] NOT NULL,
[Foreign_Key_Column] [int] NULL,
[Other_Foreign_Key_Column_2] [int] NULL,
[Column_1] [date] NOT NULL,
[Column_2] [decimal](19, 3) NOT NULL,
CONSTRAINT [Primary_Key_Name] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Our_Table] WITH CHECK ADD CONSTRAINT [Constraing_Name_1] FOREIGN KEY([Other_Foreign_Key_Column_1])
REFERENCES [dbo].[Other_Table_1] ([Id])
GO
ALTER TABLE [dbo].[Our_Table] CHECK CONSTRAINT [Constraing_Name_1]
GO
ALTER TABLE [dbo].[Our_Table] WITH CHECK ADD CONSTRAINT [Constraing_Name_2] FOREIGN KEY([Other_Foreign_Key_Column_2])
REFERENCES [dbo].[Other_Table_2] ([Id])
GO
ALTER TABLE [dbo].[Our_Table] CHECK CONSTRAINT [Constraing_Name_2]
GO
ALTER TABLE [dbo].[Our_Table] WITH CHECK ADD CONSTRAINT [Constraing_Name_3] FOREIGN KEY([Foreign_Key_Column])
REFERENCES [dbo].[Other_Table_3] ([Id])
GO
ALTER TABLE [dbo].[Our_Table] CHECK CONSTRAINT [Constraing_Name_3]
GO
第一个索引:
USE [Our_Database]
GO
/****** Object: Index [Index_Name_1] Script Date: 06.02.2024 16:24:14 ******/
CREATE NONCLUSTERED INDEX [Index_Name_1] ON [dbo].[Our_Table]
(
[Foreign_Key_Column] ASC
)
INCLUDE([Id],[Other_Foreign_Key_Column_1],[Column_1],[Other_Foreign_Key_Column_2],[Column_2]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
GO
第二个索引:
USE [Our_Database]
GO
/****** Object: Index [Index_Name_2] Script Date: 06.02.2024 16:27:00 ******/
CREATE NONCLUSTERED INDEX [Index_Name_2] ON [dbo].[Our_Table]
(
[Other_Foreign_Key_Column_1] ASC,
[Foreign_Key_Column] ASC,
[Column_1] ASC
)
INCLUDE([Id],[Other_Foreign_Key_Column_2],[Column_2]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
GO
第三:死锁 XDL/XML 图作为文本
xml_report <deadlock> <victim-list> <victimProcess id="process1eb2b5a3c28"/> </victim-list> <process-list> <process id="process1eb2b5a3c28" taskpriority="0" logused="0" waitresource="PAGE: 8:1:12279678 " waittime="7830" ownerId="60983391" transactionname="DELETE" lasttranstarted="2023-11-30T18:20:47.690" XDES="0x1ebf3b43aa0" lockMode="U" schedulerid="6" kpid="8460" status="suspended" spid="51" sbid="0" ecid="4" priority="0" trancount="0" lastbatchstarted="2023-11-30T18:20:47.680" lastbatchcompleted="2023-11-30T18:20:47.680" lastattention="1900-01-01T00:00:00.680" clientapp=".Net SqlClient Data Provider" hostname="Our_Hostname" hostpid="8724" isolationlevel="read committed (2)" xactid="60983391" currentdb="8" currentdbname="Our_Database" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056"> <executionStack> <frame procname="adhoc" line="1" stmtend="152" sqlhandle="0x02000000cff49033589c5bbc920f9e3e6c20dd52ec7b2a6b0000000000000000000000000000000000000000"> unknown </frame> <frame procname="adhoc" line="1" stmtend="152" sqlhandle="0x0200000054ea521e213370ce6f8ed8419b18c5e68fe33f980000000000000000000000000000000000000000"> unknown </frame> </executionStack> <inputbuf> delete from Our_Table where Foreign_Key_Column = 78905 </inputbuf> </process> <process id="process1eb2b5b3c28" taskpriority="0" logused="0" waitresource="PAGE: 8:1:12280105 " waittime="7827" ownerId="60983390" transactionname="DELETE" lasttranstarted="2023-11-30T18:20:47.690" XDES="0x1eb3c43cd70" lockMode="U" schedulerid="8" kpid="11956" status="suspended" spid="68" sbid="0" ecid="6" priority="0" trancount="0" lastbatchstarted="2023-11-30T18:20:47.680" lastbatchcompleted="2023-11-30T18:20:47.680" lastattention="1900-01-01T00:00:00.680" clientapp=".Net SqlClient Data Provider" hostname="Our_Hostname" hostpid="8724" isolationlevel="read committed (2)" xactid="60983390" currentdb="8" currentdbname="Our_Database" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056"> <executionStack> <frame procname="adhoc" line="1" stmtend="152" sqlhandle="0x02000000cff49033589c5bbc920f9e3e6c20dd52ec7b2a6b0000000000000000000000000000000000000000"> unknown </frame> <frame procname="adhoc" line="1" stmtend="152" sqlhandle="0x02000000ac70fc271ab95a0c0039641710cac984cc29aae00000000000000000000000000000000000000000"> unknown </frame> </executionStack> <inputbuf> delete from Our_Table where Foreign_Key_Column = 78906 </inputbuf> </process> <process id="process1eb2b5c3c28" taskpriority="0" logused="0" waitresource="PAGE: 8:1:12280105 " waittime="7817" ownerId="60983390" transactionname="DELETE" lasttranstarted="2023-11-30T18:20:47.690" XDES="0x1f1c449d050" lockMode="U" schedulerid="10" kpid="2404" status="suspended" spid="68" sbid="0" ecid="4" priority="0" trancount="0" lastbatchstarted="2023-11-30T18:20:47.680" lastbatchcompleted="2023-11-30T18:20:47.680" lastattention="1900-01-01T00:00:00.680" clientapp=".Net SqlClient Data Provider" hostname="Our_Hostname" hostpid="8724" isolationlevel="read committed (2)" xactid="60983390" currentdb="8" currentdbname="Our_Database" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056"> <executionStack> <frame procname="adhoc" line="1" stmtend="152" sqlhandle="0x02000000cff49033589c5bbc920f9e3e6c20dd52ec7b2a6b0000000000000000000000000000000000000000"> unknown </frame> <frame procname="adhoc" line="1" stmtend="152" sqlhandle="0x02000000ac70fc271ab95a0c0039641710cac984cc29aae00000000000000000000000000000000000000000"> unknown </frame> </executionStack> <inputbuf> delete from Our_Table where Foreign_Key_Column = 78906 </inputbuf> </process> <process id="process1eb2b051848" taskpriority="0" logused="0" waitresource="PAGE: 8:1:12279678 " waittime="7815" ownerId="60983391" transactionname="DELETE" lasttranstarted="2023-11-30T18:20:47.690" XDES="0x1eb3c87baa0" lockMode="U" schedulerid="1" kpid="2576" status="suspended" spid="51" sbid="0" ecid="2" priority="0" trancount="0" lastbatchstarted="2023-11-30T18:20:47.680" lastbatchcompleted="2023-11-30T18:20:47.680" lastattention="1900-01-01T00:00:00.680" clientapp=".Net SqlClient Data Provider" hostname="Our_Hostname" hostpid="8724" isolationlevel="read committed (2)" xactid="60983391" currentdb="8" currentdbname="Our_Database" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056"> <executionStack> <frame procname="adhoc" line="1" stmtend="152" sqlhandle="0x02000000cff49033589c5bbc920f9e3e6c20dd52ec7b2a6b0000000000000000000000000000000000000000"> unknown </frame> <frame procname="adhoc" line="1" stmtend="152" sqlhandle="0x0200000054ea521e213370ce6f8ed8419b18c5e68fe33f980000000000000000000000000000000000000000"> unknown </frame> </executionStack> <inputbuf> delete from Our_Table where Foreign_Key_Column = 78905 </inputbuf> </process> <process id="process1edc2d288c8" taskpriority="0" logused="10000" waittime="2963" schedulerid="4" kpid="11376" status="suspended" spid="51" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2023-11-30T18:20:47.680" lastbatchcompleted="2023-11-30T18:20:47.680" lastattention="1900-01-01T00:00:00.680" clientapp=".Net SqlClient Data Provider" hostname="Our_Hostname" hostpid="8724" loginname="Console" isolationlevel="read committed (2)" xactid="60983391" currentdb="8" currentdbname="Our_Database" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056"> <executionStack> <frame procname="adhoc" line="1" stmtend="152" sqlhandle="0x02000000cff49033589c5bbc920f9e3e6c20dd52ec7b2a6b0000000000000000000000000000000000000000"> unknown </frame> <frame procname="adhoc" line="1" stmtend="152" sqlhandle="0x0200000054ea521e213370ce6f8ed8419b18c5e68fe33f980000000000000000000000000000000000000000"> unknown </frame> </executionStack> <inputbuf> delete from Our_Table where Foreign_Key_Column = 78905 </inputbuf> </process> </process-list> <resource-list> <pagelock fileid="1" pageid="12279678" dbid="8" subresource="FULL" objectname="Our_Database.dbo.Our_Table" id="lock1ecc8c77a80" mode="U" associatedObjectId="72057661130997760"> <owner-list> <owner id="process1eb2b5c3c28" mode="U"/> </owner-list> <waiter-list> <waiter id="process1eb2b5a3c28" mode="U" requestType="wait"/> </waiter-list> </pagelock> <pagelock fileid="1" pageid="12280105" dbid="8" subresource="FULL" objectname="Our_Database.dbo.Our_Table" id="lock1ecc8d4f800" mode="U" associatedObjectId="72057661130997760"> <owner-list> <owner id="process1edc2d288c8" mode="U"/> </owner-list> <waiter-list> <waiter id="process1eb2b5b3c28" mode="U" requestType="wait"/> </waiter-list> </pagelock> <pagelock fileid="1" pageid="12280105" dbid="8" subresource="FULL" objectname="Our_Database.dbo.Our_Table" id="lock1ecc8d4f800" mode="U" associatedObjectId="72057661130997760"> <owner-list> <owner id="process1eb2b5b3c28" mode="U" requestType="wait"/> </owner-list> <waiter-list> <waiter id="process1eb2b5c3c28" mode="U" requestType="wait"/> </waiter-list> </pagelock> <pagelock fileid="1" pageid="12279678" dbid="8" subresource="FULL" objectname="Our_Database.dbo.Our_Table" id="lock1ecc8c77a80" mode="U" associatedObjectId="72057661130997760"> <owner-list> <owner id="process1eb2b5a3c28" mode="U" requestType="wait"/> </owner-list> <waiter-list> <waiter id="process1eb2b051848" mode="U" requestType="wait"/> </waiter-list> </pagelock> <exchangeEvent id="Pipe1ebc82f6380" WaitType="e_waitPipeGetRow" waiterType="Coordinator" nodeId="7" tid="0" ownerActivity="sentData" waiterActivity="needMoreData" merging="false" spilling="false" waitingToClose="false"> <owner-list> <owner id="process1eb2b051848"/> <owner id="process1eb2b5a3c28"/> </owner-list> <waiter-list> <waiter id="process1edc2d288c8"/> </waiter-list> </exchangeEvent> </resource-list> </deadlock>
第四:查询计划
您的问题始于
Other_Table_4
在 Our_Table
上有一个外键。因此,每当DELETE
上发生Our_Table
时,服务器需要检查Other_Table_4
中是否没有相关行,否则删除失败。
但这里的问题是
Other_Table_4
没有正确的索引,因此它对 Other_Table_4
进行大量扫描以查找行。然后,这会导致其他执行相同操作的查询陷入死锁。
如果表上有外键,那么子表中的该列必须有索引,否则最终会进行巨大的表扫描来查找这些行。
CREATE INDEX FKOur_Table ON Other_Table_4 (FKOur_Table)
您可以将其他列添加到键或作为
INCLUDE
,但该列 必须 是前导键列。
现在应该避免死锁,因为它们将锁定索引中的不同键,而且查询将更快地完成并且不太可能发生死锁。