适用于:SQL Server
Azure SQL 数据库
Azure SQL 托管实例
Azure Synapse Analytics
分析平台系统 (PDW)
Microsoft Fabric 预览版中的 SQL 数据库
本文深入讨论数据库引擎中的死锁。 死锁是由数据库中的竞争的并发锁引起的,通常是在多步骤事务中。 有关事务和锁的详细信息,请参阅 事务锁定和行版本控制指南。
有关在 Azure SQL 数据库和 Fabric 中的 SQL 数据库中识别和防止死锁的更具体信息,请参阅 分析并防止 Azure SQL 数据库和 Fabric 中的 SQL 数据库死锁。
了解死锁
当两个或更多个任务永久性相互阻塞(因为一个任务在另一任务尝试锁定的资源上持有锁)时,就会发生死锁。 例如:
事务 A 获取了行 1 的共享锁。
事务 B 获取了行 2 的共享锁。
现在,事务 A 请求行 2 的排他锁,并被阻止,直到事务 B 完成并释放它在行 2 上的共享锁。
现在,事务 B 请求行 1 的排他锁,并被阻止,直到事务 A 完成并释放它在行 1 上的共享锁。
事务 B 完成之后事务 A 才能完成,但是事务 B 由事务 A 阻塞。该条件也称为循环依赖关系:事务 A 依赖于事务 B,而事务 B 通过依赖于事务 A 来关闭循环。
处于死锁中的两个事务将永远等待,除非死锁被外部过程破坏。 数据库引擎死锁监视器会定期检查死锁中的任务。 如果监视器检测到循环依赖关系,它将选择其中某一任务作为牺牲品,同时终止其事务并返回错误。 这允许其他任务完成其事务。 事务因错误终止的应用程序可以重试该事务,这通常在另一个死锁事务完成后完成。
死锁经常与正常阻塞混淆。 当一个事务请求对另一个事务锁定的资源进行锁定时,请求事务会等待,直到锁被释放。 默认情况下,数据库引擎中的事务不会超时,除非设置了 LOCK_TIMEOUT。 请求事务被阻止,而不是死锁,因为请求事务没有做任何事情来阻止拥有锁的事务。 最终,拥有事务完成并释放锁,然后请求事务被授予锁并继续。 死锁几乎可以立即解决,而锁定在理论上可以无限期地持续。 死锁有时称为抱死。
死锁可能发生在任何具有多个线程的系统上,而不仅仅是关系数据库管理系统上,也可能发生在数据库对象上的锁以外的资源上。 例如,多线程操作系统中的一个线程可能获取一个或多个资源(例如,内存块)。 如果要获取的资源当前为另一线程所拥有,则第一个线程可能必须等待拥有线程释放目标资源。 等待线程被认为依赖于该特定资源的拥有线程。 在数据库引擎的实例中,获取非数据库资源(如内存或线程)时,会话可能会死锁。
在图中,事务 T1 依赖于事务 T2 的 Part 表锁资源。 同样,对于 Supplier 表锁资源,事务 T2 依赖于事务 T1。 因为这些依赖关系形成了一个循环,所以在事务 T1 和事务 T2 之间存在死锁。
下面是死锁的一个更普通示例:
任务 T1 对资源 R1 有一个锁(用从 R1 到 T1 的箭头表示),并且请求对资源 R2 有一个锁(用从 T1 到 R2 的箭头表示)。
任务 T2 对资源 R2 有一个锁(用从 R2 到 T2 的箭头表示),并且请求对资源 R1 有一个锁(用从 T2 到 R1 的箭头表示)。
因为在资源可用之前,两个任务都不能继续,而且在任务继续之前,两种资源都不能释放,所以存在死锁状态。
Note
数据库引擎会自动检测死锁周期。 它选择其中一个事务作为死锁的受害者,并通过错误终止该事务来解决死锁。
可以死锁的资源
每个用户会话可能有一个或多个任务代表它运行,其中每个任务可能会获取或等待获取资源。 以下类型的资源可能会造成阻塞,并最终导致死锁。
Locks. 等待获取资源(如对象、页、行、元数据和应用程序)的锁可能导致死锁。 例如,事务 T1 对行 r1 具有共享(
S)锁,并且正在等待在行 r2 上获取一个排他锁(X)。 事务 T2 在 r2 上持有共享(S)锁,并正在等待获取行 r1 的排他(X)锁。 这导致了一个锁循环,在这个锁循环中,T1 和 T2 相互等待对方释放被锁的资源。工作线程。 排队等待可用工作线程的任务可能导致死锁。 如果排队的任务拥有阻塞所有工作线程的资源,则会导致死锁。 例如,会话 S1 启动一个事务并在行 r1 上获取一个共享(
S)锁,然后进入休眠状态。 在所有可用的工作线程上正在运行的活动会话正在尝试获取行 r1 上的排他(X)锁。 因为会话 S1 无法获取工作线程,所以无法提交事务并释放行 r1 的锁。 这将导致死锁。Memory. 当并发请求等待可用内存无法满足的内存授予时,可能会发生死锁。 例如,两个并发查询(Q1 和 Q2)作为用户定义函数执行,分别获取 10 MB 和 20 MB 的内存。 如果每个查询需要 30 MB,而总可用内存为 20 MB,则 Q1 和 Q2 必须等待对方释放内存,这会导致死锁。
并行查询执行相关资源。 与交换端口关联的处理协调器、发生器或使用者线程可能会相互阻塞,导致死锁,通常是在包含至少一个不属于并行查询的其他进程时。 此外,当并行查询开始执行时,数据库引擎会根据当前工作负荷确定并行度和所需工作线程数。 如果系统工作负荷发生意外变化,例如,服务器上开始运行新的查询,或者系统用完工作线程,则可能发生死锁。
多个活动的结果集 (MARS) 资源。 这些资源用于控制 MARS 下多个活动请求的交错。 有关详细信息,请参阅在 SQL Server Native Client 中使用多个活动的结果集 (MARS)。
用户资源。 当线程正在等待可能由用户应用程序控制的资源时,该资源被视为外部或用户资源,并被视为锁。
会话互斥体。 在一个会话中运行的任务是交叉的,意味着在某一给定时间只能在该会话中运行一个任务。 在任务可以运行之前,它必须对会话互斥体具有独占访问权限。
事务互斥锁。 在一个事务中运行的任务是交叉的,意味着在某一给定时间只能在该事务中运行一个任务。 在任务可以运行之前,它必须对事务互斥体具有独占访问权限。
为了使任务在 MARS 下运行,它必须获取会话互斥体。 如果任务在事务下运行,则必须获取事务互斥体。 这保证了在给定的会话和给定的事务中,一次只有一个任务处于活动状态。 一旦获取了所需的互斥体,任务就可以执行。 当任务完成或在请求过程中产生时,它首先释放事务互斥体,然后释放会话互斥体,顺序与获取顺序相反。 但是,这些资源可能导致死锁。 在以下伪代码中,两个任务(用户请求 U1 和用户请求 U2)在同一会话中运行。
U1: Rs1=Command1.Execute("insert sometable EXEC usp_someproc"); U2: Rs2=Command2.Execute("select colA from sometable");从用户请求 U1 执行的存储过程已经获取了会话互斥体。 如果存储过程需要很长时间才能执行,则数据库引擎假定存储过程正在等待用户的输入。 用户请求 U2 正在等待会话互斥体,而用户正在等待来自 U2 的结果集,U1 正在等待用户资源。 死锁状态的逻辑说明如下:
当表被分区并且 LOCK_ESCALATION 的 ALTER TABLE 设置设置为 AUTO 时,也可能发生死锁。 如果将LOCK_ESCALATION设置为AUTO,则并发性会增加,因为数据库引擎允许在 HoBT(堆或 B 树)级别锁定表分区,而不是在表级别锁定表分区。 然而,当单独的事务在表中持有分区锁,并希望在其他事务分区上的某个位置拥有锁时,这会导致死锁。 通过将 LOCK_ESCALATION 设置为 TABLE 可以避免这种死锁。 但是,此设置通过强制对分区进行大型更新以等待表锁来降低并发性。
死锁检测
“可能死锁的资源”部分中列出的所有资源都参与了数据库引擎的死锁检测方案。 死锁检测由锁监视器线程执行,该线程定期启动对数据库引擎实例中所有任务的搜索。 以下几点说明了搜索进程:
默认间隔为 5 秒。
如果锁监视器线程发现死锁,死锁检测间隔将从 5 秒降至 100 毫秒,具体取决于死锁的频率。
如果锁监视器线程停止查找死锁,数据库引擎会将搜索间隔增加到 5 秒。
如果检测到死锁,可以假定必须等待锁的新线程进入死锁循环。 检测到死锁后的前几个锁等待会立即触发死锁搜索,而不是等待下一个死锁检测间隔。 例如,如果当前时间间隔为 5 秒,且刚刚检测到死锁,则下一个锁等待会立即启动死锁检测器。 如果此锁等待是死锁的一部分,那么它会被立即检测到,而不是在下一次死锁搜索时才被检测到。
数据库引擎通常仅执行定期死锁检测。 由于系统中遇到的死锁数通常很小,定期死锁检测有助于减少系统中死锁检测的开销。
当锁监视器启动对特定线程的死锁搜索时,它会识别线程正在等待的资源。 然后,锁监视器会找到该特定资源的所有者,并递归地继续对这些线程进行死锁搜索,直到找到一个循环。 以这种方式识别的循环形成死锁。
检测到死锁后,数据库引擎通过选择其中一个线程作为死锁受害者结束死锁。 数据库引擎终止线程当前执行的批处理操作,回滚死锁受害者的事务,并将错误 1205 返回给应用程序。 为死锁牺牲品回滚事务将释放该事务持有的所有锁。 这允许其他线程的事务解除阻塞并继续。 1205(死锁受害者错误)记录有关死锁中涉及的资源类型的信息。
默认情况下,数据库引擎选择回滚成本最低的事务作为被选中的死锁受害者。 此外,用户也可以使用 SET DEADLOCK_PRIORITY 语句指定死锁情况下会话的优先级。
DEADLOCK_PRIORITY 可以设置为 LOW、 NORMAL或 HIGH也可以设置为范围从 -10 到 10 的任何整数值。 在某些情况下,数据库引擎可能会选择在短时间内更改死锁优先级,以实现更好的并发性。
死锁优先级默认为 NORMAL或 0。 如果两个会话具有不同的死锁优先级,则选择具有较低优先级的会话上的事务作为死锁受害者。 如果两次会话具有相同的死锁优先级,将选择恢复时操作代价最低的事务。 如果死锁循环中涉及的会话具有相同的死锁优先级和相同的开销,则会随机选择死锁牺牲品。 正在回滚的任务不能被选为死锁牺牲品。
使用公共语言运行时(CLR)时,死锁监视器会自动检测在托管过程中访问的同步资源(监视器、读取器/编写器锁和线程联接)的死锁。 但是,通过在被选为死锁牺牲品的过程中引发异常来解决死锁。 因此,请务必理解异常不会自动释放牺牲品当前拥有的资源;必须明确释放资源。 与异常行为一致,用于识别死锁牺牲品的异常可以被捕获和排除。
死锁信息工具
若要查看死锁信息,数据库引擎提供监视工具,包括 xml_deadlock_report 扩展事件、两个跟踪标志以及 SQL Profiler 中的死锁图形事件。
推荐使用扩展 xml_deadlock_report 事件来捕获死锁信息。
死锁扩展事件
在 SQL Server 2012(11.x)及更高版本中, xml_deadlock_report 应使用扩展事件,而不是 SQL 跟踪或 SQL Profiler 中的死锁图形事件类。
默认情况下,system_health 事件会话会捕获xml_deadlock_report 事件。 这些事件中包含死锁图。
system_health由于会话默认处于启用状态,因此无需配置单独的事件会话来捕获死锁信息。
捕获的死锁图通常具有三个不同的节点:
-
victim-list。 死锁牺牲品进程标识符。 -
process-list。 死锁中涉及的全部进程的信息。 -
resource-list。 死锁中涉及的资源的信息。
可以在 Management Studio 中查看 system_health 会话中的 event_file 目标数据。 如果发生任何 xml_deadlock_report 事件,Management Studio 会显示死锁中涉及的任务和资源的图形描述,如以下示例所示:
以下查询可以查看会话system_health的目标ring_buffer捕获的所有死锁事件:
SELECT xdr.value('@timestamp', 'datetime') AS deadlock_time,
xdr.query('.') AS event_data
FROM (SELECT CAST ([target_data] AS XML) AS target_data
FROM sys.dm_xe_session_targets AS xt
INNER JOIN sys.dm_xe_sessions AS xs
ON xs.address = xt.event_session_address
WHERE xs.name = N'system_health'
AND xt.target_name = N'ring_buffer') AS XML_Data
CROSS APPLY Target_Data.nodes('RingBufferTarget/event[@name="xml_deadlock_report"]') AS XEventData(xdr)
ORDER BY deadlock_time DESC;
结果集如下。
以下示例展示了 event_data 列的输出:
<event name="xml_deadlock_report" package="sqlserver" timestamp="2022-02-18T08:26:24.698Z">
<data name="xml_report">
<type name="xml" package="package0" />
<value>
<deadlock>
<victim-list>
<victimProcess id="process27b9b0b9848" />
</victim-list>
<process-list>
<process id="process27b9b0b9848" taskpriority="0" logused="0" waitresource="KEY: 5:72057594214350848 (1a39e6095155)" waittime="1631" ownerId="11088595" transactionname="SELECT" lasttranstarted="2022-02-18T00:26:23.073" XDES="0x27b9f79fac0" lockMode="S" schedulerid="9" kpid="15336" status="suspended" spid="62" sbid="0" ecid="0" priority="0" trancount="0" lastbatchstarted="2022-02-18T00:26:22.893" lastbatchcompleted="2022-02-18T00:26:22.890" lastattention="1900-01-01T00:00:00.890" clientapp="SQLCMD" hostname="ContosoServer" hostpid="7908" loginname="CONTOSO\user" isolationlevel="read committed (2)" xactid="11088595" currentdb="5" lockTimeout="4294967295" clientoption1="538968096" clientoption2="128056">
<executionStack>
<frame procname="AdventureWorks2022.dbo.p1" line="3" stmtstart="78" stmtend="180" sqlhandle="0x0300050020766505ca3e07008ba8000001000000000000000000000000000000000000000000000000000000">
SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+ </frame>
<frame procname="adhoc" line="4" stmtstart="82" stmtend="98" sqlhandle="0x020000006263ec01ebb919c335024a072a2699958d3fcce60000000000000000000000000000000000000000">
unknown </frame>
</executionStack>
<inputbuf>
SET NOCOUNT ON
WHILE (1=1)
BEGIN
EXEC p1 4
END
</inputbuf>
</process>
<process id="process27b9ee33c28" taskpriority="0" logused="252" waitresource="KEY: 5:72057594214416384 (e5b3d7e750dd)" waittime="1631" ownerId="11088593" transactionname="UPDATE" lasttranstarted="2022-02-18T00:26:23.073" XDES="0x27ba15a4490" lockMode="X" schedulerid="6" kpid="5584" status="suspended" spid="58" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2022-02-18T00:26:22.890" lastbatchcompleted="2022-02-18T00:26:22.890" lastattention="1900-01-01T00:00:00.890" clientapp="SQLCMD" hostname="ContosoServer" hostpid="15316" loginname="CONTOSO\user" isolationlevel="read committed (2)" xactid="11088593" currentdb="5" lockTimeout="4294967295" clientoption1="538968096" clientoption2="128056">
<executionStack>
<frame procname="AdventureWorks2022.dbo.p2" line="3" stmtstart="76" stmtend="150" sqlhandle="0x03000500599a5906ce3e07008ba8000001000000000000000000000000000000000000000000000000000000">
UPDATE t1 SET c2 = c2+1 WHERE c1 = @p </frame>
<frame procname="adhoc" line="4" stmtstart="82" stmtend="98" sqlhandle="0x02000000008fe521e5fb1099410048c5743ff7da04b2047b0000000000000000000000000000000000000000">
unknown </frame>
</executionStack>
<inputbuf>
SET NOCOUNT ON
WHILE (1=1)
BEGIN
EXEC p2 4
END
</inputbuf>
</process>
</process-list>
<resource-list>
<keylock hobtid="72057594214350848" dbid="5" objectname="AdventureWorks2022.dbo.t1" indexname="cidx" id="lock27b9dd26a00" mode="X" associatedObjectId="72057594214350848">
<owner-list>
<owner id="process27b9ee33c28" mode="X" />
</owner-list>
<waiter-list>
<waiter id="process27b9b0b9848" mode="S" requestType="wait" />
</waiter-list>
</keylock>
<keylock hobtid="72057594214416384" dbid="5" objectname="AdventureWorks2022.dbo.t1" indexname="idx1" id="lock27afa392600" mode="S" associatedObjectId="72057594214416384">
<owner-list>
<owner id="process27b9b0b9848" mode="S" />
</owner-list>
<waiter-list>
<waiter id="process27b9ee33c28" mode="X" requestType="wait" />
</waiter-list>
</keylock>
</resource-list>
</deadlock>
</value>
</data>
</event>
跟踪标志 1204 和跟踪标志 1222
发生死锁并启用跟踪标志 1204 或跟踪标志 1222 时,会在 SQL Server 错误日志中报告死锁详细信息。 跟踪标志 1204 报告格式化的死锁信息,这些信息由死锁中每个涉及节点生成。 跟踪标志 1222 以首先按进程,然后按资源的顺序格式化死锁信息。 可以启用两个跟踪标志来获取同一死锁事件的两种表示。
Important
避免在遇到死锁的工作负荷密集型系统上使用跟踪标志 1204 和 1222。 使用这些跟踪标志可能会导致性能问题。 可以使用 Deadlock 扩展事件 来捕获必要的信息。
除了定义跟踪标志 1204 和 1222 的属性外,下表还显示了相似性和差异。
| Property | 跟踪标志 1204 和跟踪标志 1222 | 仅跟踪标志 1204 | 仅跟踪标志 1222 |
|---|---|---|---|
| 输出格式 | 在 SQL Server 错误日志中捕获输出。 | 重点关注死锁中涉及的节点。 每个节点都有一个专用部分,最后一部分描述死锁牺牲品。 | 以不符合 XML 架构定义 (XSD) 架构的类似 XML 格式返回信息。 该格式有三个主要部分。 第一部分声明死锁牺牲品。 第二部分说明死锁所涉及的每个进程; 第三部分介绍与跟踪标志 1204 中的节点同义的资源。 |
| 标识属性 |
SPID:<x> ECID:<x>. 标识并行进程时会话 ID 线程。 条目 SPID:<x> ECID:0(其中,<x> 将替换为 SPID 值)表示主线程。 条目 SPID:<x> ECID:<y>(由 <x> SPID 值替换且 <y> 大于 0)表示同一 SPID 的执行上下文。BatchID (sbid 对于跟踪标志 1222)。 标识代码执行从中请求或持有锁的批处理。 禁用多重活动结果集 (MARS) 后,BatchID 值为 0。 启用 MARS 后,活动批处理的值为 1 到 n。 如果会话中没有活动批处理,则 BatchID 为 0。Mode 为线程请求、授予或等待的特定资源指定锁的类型。 模式可以是意向共享(IS)、共享(S)、更新(U)、意向独占(IX)、与意向独占共享(SIX)和独占(X)。Line # (line 对于跟踪标志 1222)。 列出死锁发生时正在执行的当前语句批处理中的行号。Input Buf (inputbuf 对于跟踪标志 1222)。 列出当前批处理中的所有语句。 |
Node 表示死锁链中的条目编号。Lists 锁所有者可以是这些列表的一部分:Grant List 枚举资源的当前所有者。Convert List 枚举试图将锁转换为更高级别的当前所有者。Wait List 枚举对资源的当前新锁请求。Statement Type描述线程具有权限的语句类型(SELECT、INSERTUPDATE或DELETE)。Victim Resource Owner 指定数据库引擎选择作为牺牲品来中断死锁周期的参与线程。 所选线程及其所有执行上下文都会终止。Next Branch 表示死锁周期中涉及的同一 SPID 中的两个或多个执行上下文。 |
deadlock victim表示选为死锁牺牲品的任务的物理内存地址(请参阅 sys.dm_os_tasks)。 在未解析的死锁情况下,值可能为零。executionstack 表示在死锁发生时正在执行的 Transact-SQL 调用堆栈。priority 表示死锁优先级。logused 任务使用的日志空间。owner id 可控制请求的事务的 ID。status 任务的状态。 有关详细信息,请参阅 sys.dm_os_tasks。waitresource 任务需要的资源。waittime 等待资源的时间(毫秒)。schedulerid 与此任务关联的计划程序。 请参阅 sys.dm_os_schedulers。hostname 工作站的名称。isolationlevel 当前事务的隔离级别。Xactid 可控制请求的事务的 ID。currentdb 数据库的 ID。lastbatchstarted 客户端进程上次启动批处理执行的时间。lastbatchcompleted 客户端进程上次完成批处理执行的时间。clientoption1 和 clientoption2 会话中设置的选项。 这些值是表示选项的位掩码,通常由SET语句(如SET NOCOUNT和SET XACTABORT)控制。 有关详细信息,请参阅 @@OPTIONS。associatedObjectId 表示 HoBT(堆或 B 树)ID。 |
| 资源属性 |
RID 标识表中持有或请求锁的单行。 RID 表示为 RID:db_id:file_id:page_no:row_no。 例如,RID: 6:1:20789:0。OBJECT 标识持有锁或请求锁的表。
OBJECT 表示为 OBJECT: db_id:object_id。 例如,TAB: 6:2009058193。KEY 标识索引中持有锁或请求锁的键范围。 KEY 表示为 KEY:db_id:hobt_id(索引键哈希值)。 例如,KEY: 6:72057594057457664 (350007a4d329)。PAG 标识持有锁或请求锁的页资源。
PAG 表示为 PAG: db_id:file_id:page_no。 例如,PAG: 6:1:20789。EXT 标识盘区结构。
EXT 表示为 EXT: db_id:file_id:extent_no。 例如,EXT: 6:1:9。DB 标识数据库锁。
DB 以下列方式之一表示:DB: db_idDB: db_id[BULK-OP-DB],用于标识由数据库备份获取的数据库锁。DB: db_id[BULK-OP-LOG],用于标识日志备份采取的锁。APP 标识应用程序锁。
APP 表示为 APP: lock_resource。 例如,APP: Formf370f478。METADATA 表示死锁中涉及的元数据资源。 因为 METADATA 有许多子资源,所以返回的值取决于死锁的子资源。 例如,METADATA.USER_TYPE 返回 user_type_id = *integer_value*。 有关 METADATA 资源和子资源的详细信息,请参阅 sys.dm_tran_locks。HOBT表示死锁中涉及的堆或 B 树。 |
并非此跟踪标志所独有。 | 并非此跟踪标志所独有。 |
跟踪标志 1204 示例
以下示例显示启用跟踪标志 1204 时的输出。 在此示例中,节点 1 中的表是一个没有索引的堆,而节点 2 中的表是一个具有非聚集索引的堆。 当死锁发生时,节点 2 中的索引键正在更新。
Deadlock encountered .... Printing deadlock information
Wait-for graph
Node:1
RID: 6:1:20789:0 CleanCnt:3 Mode:X Flags: 0x2
Grant List 0:
Owner:0x0315D6A0 Mode: X
Flg:0x0 Ref:0 Life:02000000 SPID:55 ECID:0 XactLockInfo: 0x04D9E27C
SPID: 55 ECID: 0 Statement Type: UPDATE Line #: 6
Input Buf: Language Event:
BEGIN TRANSACTION
EXEC usp_p2
Requested By:
ResType:LockOwner Stype:'OR'Xdes:0x03A3DAD0
Mode: U SPID:54 BatchID:0 ECID:0 TaskProxy:(0x04976374) Value:0x315d200 Cost:(0/868)
Node:2
KEY: 6:72057594057457664 (350007a4d329) CleanCnt:2 Mode:X Flags: 0x0
Grant List 0:
Owner:0x0315D140 Mode: X
Flg:0x0 Ref:0 Life:02000000 SPID:54 ECID:0 XactLockInfo: 0x03A3DAF4
SPID: 54 ECID: 0 Statement Type: UPDATE Line #: 6
Input Buf: Language Event:
BEGIN TRANSACTION
EXEC usp_p1
Requested By:
ResType:LockOwner Stype:'OR'Xdes:0x04D9E258
Mode: U SPID:55 BatchID:0 ECID:0 TaskProxy:(0x0475E374) Value:0x315d4a0 Cost:(0/380)
Victim Resource Owner:
ResType:LockOwner Stype:'OR'Xdes:0x04D9E258
Mode: U SPID:55 BatchID:0 ECID:0 TaskProxy:(0x0475E374) Value:0x315d4a0 Cost:(0/380)
跟踪标志 1222 示例
以下示例显示启用跟踪标志 1222 时的输出。 在此示例中,一个表是没有索引的堆,另一个表则是具有非聚集索引的堆。 在第二个表中,当死锁发生时,索引键正在更新。
deadlock-list
deadlock victim=process689978
process-list
process id=process6891f8 taskpriority=0 logused=868
waitresource=RID: 6:1:20789:0 waittime=1359 ownerId=310444
transactionname=user_transaction
lasttranstarted=2022-02-05T11:22:42.733 XDES=0x3a3dad0
lockMode=U schedulerid=1 kpid=1952 status=suspended spid=54
sbid=0 ecid=0 priority=0 transcount=2
lastbatchstarted=2022-02-05T11:22:42.733
lastbatchcompleted=2022-02-05T11:22:42.733
clientapp=Microsoft SQL Server Management Studio - Query
hostname=TEST_SERVER hostpid=2216 loginname=DOMAIN\user
isolationlevel=read committed (2) xactid=310444 currentdb=6
lockTimeout=4294967295 clientoption1=671090784 clientoption2=390200
executionStack
frame procname=AdventureWorks2022.dbo.usp_p1 line=6 stmtstart=202
sqlhandle=0x0300060013e6446b027cbb00c69600000100000000000000
UPDATE T2 SET COL1 = 3 WHERE COL1 = 1;
frame procname=adhoc line=3 stmtstart=44
sqlhandle=0x01000600856aa70f503b8104000000000000000000000000
EXEC usp_p1
inputbuf
BEGIN TRANSACTION
EXEC usp_p1
process id=process689978 taskpriority=0 logused=380
waitresource=KEY: 6:72057594057457664 (350007a4d329)
waittime=5015 ownerId=310462 transactionname=user_transaction
lasttranstarted=2022-02-05T11:22:44.077 XDES=0x4d9e258 lockMode=U
schedulerid=1 kpid=3024 status=suspended spid=55 sbid=0 ecid=0
priority=0 transcount=2 lastbatchstarted=2022-02-05T11:22:44.077
lastbatchcompleted=2022-02-05T11:22:44.077
clientapp=Microsoft SQL Server Management Studio - Query
hostname=TEST_SERVER hostpid=2216 loginname=DOMAIN\user
isolationlevel=read committed (2) xactid=310462 currentdb=6
lockTimeout=4294967295 clientoption1=671090784 clientoption2=390200
executionStack
frame procname=AdventureWorks2022.dbo.usp_p2 line=6 stmtstart=200
sqlhandle=0x030006004c0a396c027cbb00c69600000100000000000000
UPDATE T1 SET COL1 = 4 WHERE COL1 = 1;
frame procname=adhoc line=3 stmtstart=44
sqlhandle=0x01000600d688e709b85f8904000000000000000000000000
EXEC usp_p2
inputbuf
BEGIN TRANSACTION
EXEC usp_p2
resource-list
ridlock fileid=1 pageid=20789 dbid=6 objectname=AdventureWorks2022.dbo.T2
id=lock3136940 mode=X associatedObjectId=72057594057392128
owner-list
owner id=process689978 mode=X
waiter-list
waiter id=process6891f8 mode=U requestType=wait
keylock hobtid=72057594057457664 dbid=6 objectname=AdventureWorks2022.dbo.T1
indexname=nci_T1_COL1 id=lock3136fc0 mode=X
associatedObjectId=72057594057457664
owner-list
owner id=process6891f8 mode=X
waiter-list
waiter id=process689978 mode=U requestType=wait
探查器死锁图形事件
SQL Profiler 有一个事件,它呈现死锁中涉及的任务和资源的图形描述。 以下示例显示启用死锁图形事件时 SQL Profiler 的输出。
SQL 探查器和 SQL 跟踪功能已弃用,并替换为扩展事件。 扩展事件的性能开销较小,并且比 SQL 跟踪更可配置。 请考虑使用 扩展事件死锁事件 ,而不是跟踪 SQL Profiler 中的死锁。
有关死锁事件的详细信息,请参阅 Lock:Deadlock 事件类。 有关 SQL 探查器死锁图的详细信息,请参阅“保存死锁图”(SQL Server Profiler)。
扩展事件提供 SQL 跟踪事件类的等效项。 有关详细信息,请参阅 查看与 SQL 跟踪事件类等效的扩展事件。 建议使用扩展事件代替 SQL 跟踪。
处理死锁
当数据库引擎的实例选择事务作为死锁受害者时,它会终止当前批处理,回滚事务,并将错误 1205 返回到应用程序。 返回的消息的结构如下:
Your transaction (process ID #...) was deadlocked on {lock | communication buffer | thread} resources with another process and has been chosen as the deadlock victim. Rerun your transaction.
由于可以将提交 Transact-SQL 查询的任何应用程序选为死锁受害者,因此应用程序应具有可以处理错误 1205 的错误处理程序。 如果应用程序没有处理错误,应用程序可能会不知情地继续运行,此时其事务已经被回滚。
实现捕获错误 1205 的错误处理程序允许应用程序处理死锁并采取修正作(例如,自动重新提交死锁中涉及的查询)。
应用程序在重新提交其查询前应短暂暂停。 这样,死锁中涉及的其他事务就有机会得以完成并释放其锁。 重新提交查询请求其锁时,随机化暂停的持续时间可最大程度地减少死锁再次发生的可能性。 例如,可以将错误处理程序编码为在 1 到 3 秒之间的随机持续时间内暂停。
使用 TRY...CATCH 进行处理
可以使用 TRY...CATCH 处理死锁。 错误 1205 可由 CATCH 模块捕获。
有关详细信息,请参阅处理死锁。
最小化死锁
尽管死锁不能完全避免,但遵守特定的编码约定可以将发生死锁的可能性降至最低。 将死锁减至最少可以增加事务的吞吐量并减少系统开销,因为只有很少的事务:
- 回滚,撤消事务执行的所有工作。
- 由于死锁时回滚而由应用程序重新提交。
若要最大程度减少死锁,请执行以下操作:
- 按同一顺序访问对象。
- 避免事务中的用户交互。
- 保持事务简短并处于一个批处理中。
- 在不需要时,应避免使用更高的隔离级别,例如
REPEATABLE READ和SERIALIZABLE。 - 使用基于行版本控制的隔离级别。
- 启用
READ_COMMITTED_SNAPSHOT数据库选项,使使用READ COMMITTED隔离级别的事务使用行版本控制。 - 使用快照隔离事务。
- 启用
- 使用绑定连接。
按同一顺序访问对象
如果所有并发事务都以相同的顺序访问对象,则死锁不太可能发生。 例如,如果两个并发事务先获取 Supplier 表上的锁,然后获取 Part 表上的锁,则在其中一个事务完成之前,另一个事务将在 Supplier 表上被阻塞。 在第一个事务提交或回滚后,第二个事务继续进行,不会发生死锁。 对所有数据修改使用存储过程可以使访问对象的顺序标准化。
避免事务中的用户交互
避免包含用户交互的事务,因为无需用户干预的批处理速度比用户必须手动响应查询的速度快得多,例如回复应用程序请求的参数的提示。 这将降低系统的吞吐量,因为事务持有的任何锁只有在事务提交或回滚后才会释放。 即使没有发生死锁,访问相同资源的其他事务也会在等待事务完成时被阻止。
保持事务简短并处于一个批处理中
在同一数据库中并发执行多个需要长时间运行的事务时通常会发生死锁。 事务的运行时间越长,它持有排他锁或更新锁的时间也就越长,从而会阻塞其他活动并可能导致死锁。
将事务保存在一个批处理中可最大程度地减少事务期间的网络往返,从而减少由于客户端处理而导致完成事务的可能延迟。
避免更高的隔离级别
确定事务是否可以在较低的隔离级别上运行。 使用 READ COMMITTED 允许事务读取以前由另一个事务读取(但未修改)的数据,而无需等待事务完成。
READ COMMITTED 持有共享锁的时间比持有更高隔离级别(例如 SERIALIZABLE)时更短。 这可以减少锁争用。
使用基于行版本控制的隔离级别
READ_COMMITTED_SNAPSHOT 设置 ON 数据库选项时,在 READ COMMITTED 隔离级别下运行的事务在读取操作期间使用行版本而不是共享锁。
小窍门
Microsoft建议所有应用程序使用READ COMMITTED行版本控制隔离级别,除非应用程序依赖于基于READ COMMITTED锁的隔离级别的阻止行为。
快照隔离还使用行版本控制,在读取操作期间不使用共享锁。 必须将 ALLOW_SNAPSHOT_ISOLATION 数据库选项设置为 ON,事务才能在快照隔离下运行。
使用基于行版本控制的隔离级别来尽量减少读取和写入操作之间可能发生的死锁。
使用绑定连接
使用绑定连接,同一应用程序打开的两个或多个连接可以相互协作。 由辅助连接获取的任何锁都会被保存,就像它们是由主连接获取的一样,反之亦然。 因此,它们不会互相阻止。
导致死锁
出于学习或演示目的,可能需要造成死锁。
以下示例在AdventureWorksLT2019启用了READ_COMMITTED_SNAPSHOT时,使用默认架构和数据的示例数据库中工作。 要下载此示例,请访问 AdventureWorks 示例数据库。
有关启用优化锁定时导致死锁的示例,请参阅 优化锁定和死锁。
若要造成死锁,你需要将两个会话连接到 AdventureWorksLT2019 数据库。 我们将这些会话称为 会话 A 和 会话 B。可以通过在 SQL Server Management Studio(SSMS)中创建两个查询窗口来创建这两个会话。
在 会话 A 中,运行以下批处理。 此代码开始 显式事务 并执行更新表的 SalesLT.Product 语句。 为此,事务在表中符合条件的行上获取更新(U)锁,然后转换为独占(X)锁。 我们将该事务保持打开状态。
BEGIN TRANSACTION;
UPDATE SalesLT.Product
SET SellEndDate = SellEndDate + 1
WHERE Color = 'Red';
现在,在 会话 B 中,运行以下批处理。 此代码不会显式开始一个事务, 而是在自动提交事务模式下运行。 此语句更新 SalesLT.ProductDescription 表。 更新对表中符合条件的行U进行更新 (SalesLT.ProductDescription) 锁定。 查询将联接到其他表,包括 SalesLT.Product 表。
UPDATE SalesLT.ProductDescription
SET Description = Description
FROM SalesLT.ProductDescription AS pd
INNER JOIN SalesLT.ProductModelProductDescription AS pmpd
ON pd.ProductDescriptionID = pmpd.ProductDescriptionID
INNER JOIN SalesLT.ProductModel AS pm
ON pmpd.ProductModelID = pm.ProductModelID
INNER JOIN SalesLT.Product AS p
ON pm.ProductModelID = p.ProductModelID
WHERE p.Color = 'Silver';
若要完成此更新,会话 B 需要在表SalesLT.Product中的行上获取共享(S)锁,包括会话 A所锁定的行上。会话 B 被SalesLT.Product阻止。
返回到 会话 A。运行以下 UPDATE 语句。 此语句作为以前打开的事务的一部分执行。
UPDATE SalesLT.ProductDescription
SET Description = Description
FROM SalesLT.ProductDescription AS pd
INNER JOIN SalesLT.ProductModelProductDescription AS pmpd
ON pd.ProductDescriptionID = pmpd.ProductDescriptionID
INNER JOIN SalesLT.ProductModel AS pm
ON pmpd.ProductModelID = pm.ProductModelID
INNER JOIN SalesLT.Product AS p
ON pm.ProductModelID = p.ProductModelID
WHERE p.Color = 'Red';
会话 A 中的第二个 update 语句被 上的SalesLT.ProductDescription 阻塞。
会话 A 和会话 B 现在相互阻塞。 两个事务都无法继续,因为它们都需要另一个事务锁定的资源。
几秒钟后,死锁监视器将识别出会话 A 和会话 B 中的事务正在相互阻塞,并且两者都无法取得进展。 你会看到发生死锁,会话 A 被选为死锁受害者。 会话 B 成功完成。 会话 A 的查询窗口中会显示一条错误消息,其文本类似于以下示例:
Msg 1205, Level 13, State 51, Line 7
Transaction (Process ID 51) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
如果没有引发死锁,请验证示例数据库中是否启用了 READ_COMMITTED_SNAPSHOT。 死锁可能发生在任何数据库配置中,但此示例要求启用 READ_COMMITTED_SNAPSHOT。
可以在事件会话的system_health目标ring_buffer中查看死锁的详细信息,该事件会话在 SQL Server 和 Azure SQL 托管实例中默认处于启用和活动状态。 请考虑下列查询:
WITH cteDeadLocks ([Deadlock_XML])
AS (SELECT CAST (target_data AS XML) AS [Deadlock_XML]
FROM sys.dm_xe_sessions AS xs
INNER JOIN sys.dm_xe_session_targets AS xst
ON xs.[address] = xst.event_session_address
WHERE xs.[name] = 'system_health'
AND xst.target_name = 'ring_buffer')
SELECT x.Graph.query('(event/data/value/deadlock)[1]') AS Deadlock_XML,
x.Graph.value('(event/data/value/deadlock/process-list/process/@lastbatchstarted)[1]', 'datetime2(3)') AS when_occurred,
DB_Name(x.Graph.value('(event/data/value/deadlock/process-list/process/@currentdb)[1]', 'int')) AS DB --Current database of the first listed process
FROM (SELECT Graph.query('.') AS Graph
FROM cteDeadLocks AS c
CROSS APPLY c.[Deadlock_XML].nodes('RingBufferTarget/event[@name="xml_deadlock_report"]') AS Deadlock_Report(Graph)) AS x
ORDER BY when_occurred DESC;
你可以通过选择将显示为超链接的单元格来查看 SSMS 内 Deadlock_XML 列中的 XML。 将此输出保存为 .xdl 文件,关闭,然后在 SSMS 中重新打开 .xdl 文件以获得可视死锁图。 死锁图应如下图所示。
优化的锁定和死锁
通过 优化的锁定,在事务结束之前,不会保留页锁和行锁。 一旦行被更新,它们就会被释放。 此外,如果启用了READ_COMMITTED_SNAPSHOT,则不会使用更新(U)锁。 因此,死锁的可能性会降低。
上一个示例在启用优化锁定时不会导致死锁,因为它依赖于更新锁(U)。
以下示例可用于对启用了优化锁定的数据库造成死锁。
首先,创建示例表并添加数据。
CREATE TABLE t2
(
a INT PRIMARY KEY NOT NULL,
b INT NULL
);
INSERT INTO t2
VALUES (1, 10),
(2, 20),
(3, 30);
以下 T-SQL 批处理在两个单独的会话中按顺序执行,创建死锁。
在会话 1 中:
BEGIN TRANSACTION xactA;
UPDATE t2
SET b = b + 10
WHERE a = 1;
在会话 2 中:
BEGIN TRANSACTION xactB;
UPDATE t2
SET b = b + 10
WHERE a = 2;
在会话 1 中:
UPDATE t2
SET b = b + 100
WHERE a = 2;
在会话 2 中:
UPDATE t2
SET b = b + 20
WHERE a = 1;
在这种情况下,每个会话在其自己的事务 ID (TID)资源上持有一个独占锁X,并且正在等待另一个 TID 上的共享锁S,从而导致死锁情况。
以下概要的死锁报表包含特定于优化锁定的元素和属性。 在死锁报表 <resource-list>中的每个资源下,每个 <xactlock> 元素都会报告死锁的每个成员的基础资源和 TID 锁信息。
<deadlock>
<victim-list>
<victimProcess id="process12994344c58" />
</victim-list>
<process-list>
<process id="process12994344c58" taskpriority="0" logused="272" waitresource="XACT: 23:2476:0 KEY: 23:72057594049593344 (8194443284a0)" waittime="447" ownerId="3234906" transactionname="xactA" lasttranstarted="2025-10-08T21:36:34.063" XDES="0x12984ba0480" lockMode="S" schedulerid="2" kpid="204928" status="suspended" spid="95" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2025-10-08T21:36:40.857" lastbatchcompleted="2025-10-08T21:36:34.063" lastattention="2025-10-08T21:36:11.340" clientapp="Microsoft SQL Server Management Studio - Query" hostname="WS1" hostpid="23380" loginname="user1" isolationlevel="read committed (2)" xactid="3234906" currentdb="23" currentdbname="AdventureWorksLT" lockTimeout="4294967295" clientoption1="671090784" clientoption2="390200">
<inputbuf>
UPDATE t2
SET b = b + 20
WHERE a = 1;
</inputbuf>
</process>
<process id="process1299c969828" taskpriority="0" logused="272" waitresource="XACT: 23:2477:0 KEY: 23:72057594049593344 (61a06abd401c)" waittime="3083" ownerId="3234886" transactionname="xactB" lasttranstarted="2025-10-08T21:36:30.303" XDES="0x12995c84480" lockMode="S" schedulerid="2" kpid="63348" status="suspended" spid="88" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2025-10-08T21:36:38.223" lastbatchcompleted="2025-10-08T21:36:30.303" lastattention="1900-01-01T00:00:00.303" clientapp="Microsoft SQL Server Management Studio - Query" hostname="WS1" hostpid="23380" loginname="user1" isolationlevel="read committed (2)" xactid="3234886" currentdb="23" currentdbname="AdventureWorksLT" lockTimeout="4294967295" clientoption1="671090784" clientoption2="390200">
<inputbuf>
UPDATE t2
SET b = b + 100
WHERE a = 2;
</inputbuf>
</process>
</process-list>
<resource-list>
<xactlock xdesIdLow="2476" xdesIdHigh="0" dbid="23" id="lock1299fa06c00" mode="X">
<UnderlyingResource>
<keylock hobtid="72057594049593344" dbid="23" objectname="e6fc405e-1ee8-49df-a2b3-54ee0151d851.dbo.t2" indexname="PK__t2__3BD0198ED3CBA65E" />
</UnderlyingResource>
<owner-list>
<owner id="process1299c969828" mode="X" />
</owner-list>
<waiter-list>
<waiter id="process12994344c58" mode="S" requestType="wait" />
</waiter-list>
</xactlock>
<xactlock xdesIdLow="2477" xdesIdHigh="0" dbid="23" id="lock129940b2380" mode="X">
<UnderlyingResource>
<keylock hobtid="72057594049593344" dbid="23" objectname="e6fc405e-1ee8-49df-a2b3-54ee0151d851.dbo.t2" indexname="PK__t2__3BD0198ED3CBA65E" />
</UnderlyingResource>
<owner-list>
<owner id="process12994344c58" mode="X" />
</owner-list>
<waiter-list>
<waiter id="process1299c969828" mode="S" requestType="wait" />
</waiter-list>
</xactlock>
</resource-list>
</deadlock>