适用于:SQL Server
Azure SQL 数据库
Azure SQL 托管实例
Azure Synapse Analytics
分析平台系统 (PDW)
Microsoft Fabric 预览版中的 SQL 数据库
本文深入讨论 SQL Server 数据库引擎中的死锁。 死锁是由数据库中的竞争的并发锁引起的,通常是在多步骤事务中。 有关事务锁定的详细信息,请参阅事务锁定和行版本控制指南。
有关识别和防止 Azure SQL 数据库中死锁的更具体信息,请参阅 分析和防止 Azure SQL 数据库和 Fabric SQL 数据库中的死锁。
Understand deadlocks
当两个或更多个任务永久性相互阻塞(因为一个任务在另一任务尝试锁定的资源上持有锁)时,就会发生死锁。 For example:
事务 A 获取了行 1 的共享锁。
事务 B 获取了行 2 的共享锁。
现在,事务 A 请求行 2 的排他锁,并被阻止,直到事务 B 完成并释放它在行 2 上的共享锁。
现在,事务 B 请求行 1 的排他锁,并被阻止,直到事务 A 完成并释放它在行 1 上的共享锁。
事务 B 完成之后事务 A 才能完成,但是事务 B 由事务 A 阻塞。该条件也称为循环依赖关系:事务 A 依赖于事务 B,而事务 B 通过依赖于事务 A 来关闭循环。
处于死锁中的两个事务将永远等待,除非死锁被外部过程破坏。 SQL Server 数据库引擎死锁监视器定期检查陷入死锁的任务。 如果监视器检测到循环依赖关系,它将选择其中某一任务作为牺牲品,同时终止其事务并返回错误。 这允许其他任务完成其事务。 事务因错误终止的应用程序可以重试该事务,这通常在另一个死锁事务完成后完成。
死锁经常与正常阻塞混淆。 当一个事务请求对另一个事务锁定的资源进行锁定时,请求事务会等待,直到锁被释放。 默认情况下,SQL Server 事务不会超时,除非设置了 LOCK_TIMEOUT
。 请求事务被阻止,而不是死锁,因为请求事务没有做任何事情来阻止拥有锁的事务。 最终,拥有事务完成并释放锁,然后请求事务被授予锁并继续。 死锁几乎可以立即解决,而锁定在理论上可以无限期地持续。 死锁有时称为抱死。
死锁可能发生在任何具有多个线程的系统上,而不仅仅是关系数据库管理系统上,也可能发生在数据库对象上的锁以外的资源上。 例如,多线程操作系统中的一个线程可能获取一个或多个资源(例如,内存块)。 如果要获取的资源当前为另一线程所拥有,则第一个线程可能必须等待拥有线程释放目标资源。 等待线程被认为依赖于该特定资源的拥有线程。 在 SQL Server 数据库引擎的实例中,当获取非数据库资源(例如,内存或线程)时,会话可能会死锁。
在图中,事务 T1 依赖于事务 T2 的 Part
表锁资源。 同样,对于 Supplier
表锁资源,事务 T2 依赖于事务 T1。 因为这些依赖关系形成了一个循环,所以在事务 T1 和事务 T2 之间存在死锁。
当表被分区并且 LOCK_ESCALATION
的 ALTER TABLE
设置设置为 AUTO
时,也可能发生死锁。 当 LOCK_ESCALATION
设为 AUTO
时,通过允许 SQL Server 数据库引擎在 HoBT 级别而非表级别锁定表分区会增加并发情况。 然而,当单独的事务在表中持有分区锁,并希望在其他事务分区上的某个位置拥有锁时,这会导致死锁。 通过将 LOCK_ESCALATION
设置为 TABLE
可以避免这种死锁。 但是,此设置通过强制对分区进行大型更新以等待表锁来降低并发性。
检测和结束死锁
当两个或更多个任务永久性相互阻塞(因为一个任务在另一任务尝试锁定的资源上持有锁)时,就会发生死锁。 下图清楚地显示了死锁状态,其中:
任务 T1 对资源 R1 有一个锁(用从 R1 到 T1 的箭头表示),并且请求对资源 R2 有一个锁(用从 T1 到 R2 的箭头表示)。
任务 T2 对资源 R2 有一个锁(用从 R2 到 T2 的箭头表示),并且请求对资源 R1 有一个锁(用从 T2 到 R1 的箭头表示)。
因为在资源可用之前,两个任务都不能继续,而且在任务继续之前,两种资源都不能释放,所以存在死锁状态。
SQL Server 数据库引擎会自动检测 SQL Server 内的死锁循环。 SQL Server 数据库引擎选择其中一个会话作为死锁牺牲品,然后终止当前事务(出现错误)来打断死锁。
可以死锁的资源
每个用户会话可能有一个或多个任务代表它运行,其中每个任务可能会获取或等待获取资源。 以下类型的资源可能会造成阻塞,并最终导致死锁。
Locks. 等待获取资源(如对象、页、行、元数据和应用程序)的锁可能导致死锁。 例如,事务 T1 在行 r1 上有一个共享锁 (S),正在等待在行 r2 上获得一个排他锁 (X)。 事务 T2 在行 r2 上有一个共享锁 (S),正在等待在行 r1 上获得一个排他锁 (X)。 这导致了一个锁循环,在这个锁循环中,T1 和 T2 相互等待对方释放被锁的资源。
Worker threads. 排队等待可用工作线程的任务可能导致死锁。 如果排队的任务拥有阻塞所有工作线程的资源,则会导致死锁。 例如,会话 S1 启动事务并获取行 r1 的共享锁 (S) 后,进入睡眠状态。 在所有可用工作线程上运行的活动会话正试图获取行 r1 上的排他锁 (X)。 因为会话 S1 无法获取工作线程,所以无法提交事务并释放行 r1 的锁。 这将导致死锁。
Memory. 当并发请求等待可用内存无法满足的内存授予时,可能会发生死锁。 例如,两个并发查询(Q1 和 Q2)作为用户定义函数执行,分别获取 10 MB 和 20 MB 的内存。 如果每个查询需要 30 MB,而总可用内存为 20 MB,则 Q1 和 Q2 必须等待对方释放内存,这会导致死锁。
并行查询执行相关资源。 与交换端口关联的处理协调器、发生器或使用者线程可能会相互阻塞,导致死锁,通常是在包含至少一个不属于并行查询的其他进程时。 此外,当并行查询启动执行时,SQL Server 将根据当前的工作负载确定并行度或工作线程数。 如果系统工作负荷发生意外变化,例如,服务器上开始运行新的查询,或者系统用完工作线程,则可能发生死锁。
多个活动的结果集 (MARS) 资源。 这些资源用于控制 MARS 下多个活动请求的交错。 有关详细信息,请参阅在 SQL Server Native Client 中使用多个活动的结果集 (MARS)。
User resource. 当线程正在等待可能由用户应用程序控制的资源时,该资源被视为外部或用户资源,并被视为锁。
Session mutex. 在一个会话中运行的任务是交叉的,意味着在某一给定时间只能在该会话中运行一个任务。 在任务可以运行之前,它必须对会话互斥体具有独占访问权限。
Transaction mutex. 在一个事务中运行的任务是交叉的,意味着在某一给定时间只能在该事务中运行一个任务。 在任务可以运行之前,它必须对事务互斥体具有独占访问权限。
为了使任务在 MARS 下运行,它必须获取会话互斥体。 如果任务在事务下运行,则必须获取事务互斥体。 这保证了在给定的会话和给定的事务中,一次只有一个任务处于活动状态。 一旦获取了所需的互斥体,任务就可以执行。 当任务完成或在请求过程中产生时,它首先释放事务互斥体,然后释放会话互斥体,顺序与获取顺序相反。 但是,这些资源可能导致死锁。 在以下伪代码中,两个任务(用户请求 U1 和用户请求 U2)在同一会话中运行。
U1: Rs1=Command1.Execute("insert sometable EXEC usp_someproc"); U2: Rs2=Command2.Execute("select colA from sometable");
从用户请求 U1 执行的存储过程已经获取了会话互斥体。 如果执行该存储过程花费了很长时间,SQL Server 数据库引擎会认为存储过程正在等待用户的输入。 用户请求 U2 正在等待会话互斥体,而用户正在等待来自 U2 的结果集,U1 正在等待用户资源。 死锁状态的逻辑说明如下:
Deadlock detection
可以死锁的资源部分中列出的所有资源均参与 SQL Server 数据库引擎死锁检测方案。 死锁检测是由锁监视器线程执行的,该线程定期搜索 SQL Server 数据库引擎实例中的所有任务。 以下几点说明了搜索进程:
默认间隔为 5 秒。
如果锁监视器线程发现死锁,死锁检测间隔将从 5 秒降至 100 毫秒,具体取决于死锁的频率。
如果锁监视器线程停止查找死锁,SQL Server 数据库引擎会将两个搜索间的时间间隔增加到 5 秒。
如果检测到死锁,则假定必须等待锁的下一个线程正进入死锁循环。 检测到死锁后的前几个锁等待会立即触发死锁搜索,而不是等待下一个死锁检测间隔。 例如,如果当前时间间隔为 5 秒,且刚刚检测到死锁,则下一个锁等待会立即启动死锁检测器。 如果此锁等待是死锁的一部分,则会立即检测到,而不是在下一个死锁搜索期间检测到。
通常,SQL Server 数据库引擎仅定期执行死锁检测。 由于系统中遇到的死锁数量通常很少,因此定期进行死锁检测有助于减少系统中死锁检测的开销。
当锁监视器启动对特定线程的死锁搜索时,它会识别线程正在等待的资源。 然后,锁监视器会找到该特定资源的所有者,并递归地继续对这些线程进行死锁搜索,直到找到一个循环。 以这种方式识别的循环形成死锁。
检测到死锁后,SQL Server 数据库引擎通过选择其中一个线程作为死锁牺牲品来结束死锁。 SQL Server 数据库引擎会终止正为线程执行的当前批处理,回滚死锁牺牲品的事务,并将 1205 错误返回到应用程序。 为死锁牺牲品回滚事务将释放该事务持有的所有锁。 这允许其他线程的事务解除阻塞并继续。 1205 死锁牺牲品错误将有关死锁涉及的线程和资源的信息记录在错误日志中。
默认情况下,SQL Server 数据库引擎选择运行回滚开销最小的事务的会话作为死锁牺牲品。 此外,用户也可以使用 SET DEADLOCK_PRIORITY
语句指定死锁情况下会话的优先级。 可以将 DEADLOCK_PRIORITY
设置为 LOW
、NORMAL
或 HIGH
,也可以将其设置为范围(-10 到 10)间的任一整数值。 死锁优先级的默认设置为 NORMAL
。 如果两个会话的死锁优先级不同,则会选择优先级较低的会话作为死锁牺牲品。 如果两个会话具有相同的死锁优先级,则选择具有回滚开销最小的事务的会话。 如果死锁循环中涉及的会话具有相同的死锁优先级和相同的开销,则会随机选择死锁牺牲品。
使用公共语言运行时 (CLR) 时,死锁监视器将自动检测托管过程中访问的同步资源(监视器、读取器/编写器锁和线程联接)的死锁。 但是,通过在被选为死锁牺牲品的过程中引发异常来解决死锁。 因此,请务必理解异常不会自动释放牺牲品当前拥有的资源;必须明确释放资源。 与异常行为一致,用于识别死锁牺牲品的异常可以被捕获和排除。
死锁信息工具
要查看死锁信息,SQL Server 数据库引擎提供了监视工具,分别为 SQL Profiler 中的 system_health
XEvent 会话、两个跟踪标志以及死锁图形事件。
Note
本部分包含有关扩展事件、跟踪标志和跟踪的信息,但死锁扩展事件是捕获死锁信息的建议方法。
死锁扩展事件
在 SQL Server 2012 (11.x) 及更高版本中,应使用 xml_deadlock_report
扩展事件 (XEvent),而不使用 SQL 跟踪或 SQL Profiler 中的死锁图事件类。
当死锁发生时,system_health
会话已经捕获了包含死锁图的所有 xml_deadlock_report
XEvent。 由于默认情况下启用了 system_health
会话,因此不需要配置单独的 XEvent 会话来捕获死锁信息。 无需执行任何其他操作即可使用 xml_deadlock_report
XEvent 捕获死锁信息。
捕获的死锁图通常具有三个不同的节点:
-
victim-list
. 死锁牺牲品进程标识符。 -
process-list
. 死锁中涉及的全部进程的信息。 -
resource-list
. 死锁中涉及的资源的信息。
如果记录 system_health
XEvent,打开 xml_deadlock_report
会话文件或环形缓冲区,Management Studio 会显示死锁中涉及的任务和资源的图形描述,如以下示例所示:
以下查询可以查看 system_health
会话环形缓冲区捕获的所有死锁事件:
SELECT xdr.value('@timestamp', 'datetime') AS [Date],
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 [Date] 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>
如果您想捕获导致死锁的计划,请参阅 使用 xEvents 捕获有效执行计划。
有关详细信息,请参阅使用 system_health 会话
跟踪标志 1204 和跟踪标志 1222
发生死锁时,跟踪标志 1204 和跟踪标志 1222 会返回在 SQL Server 错误日志中捕获的信息。 跟踪标志 1204 会报告由死锁所涉及的每个节点设置格式的死锁信息。 跟踪标志 1222 会设置死锁信息的格式,顺序为先按进程,然后按资源。 可以启用两个跟踪标志来获取同一死锁事件的两种表示。
Important
避免在导致死锁的工作负载密集型系统上使用跟踪标志 1204 和 1222。 使用这些跟踪标志可能会导致性能问题。 请改用死锁扩展事件捕获必要的信息。
除了定义跟踪标志 1204 和 1222 的属性之外,下表还显示了它们之间的相似之处和不同之处。
Property | 跟踪标志 1204 和跟踪标志 1222 | 仅跟踪标志 1204 | 仅跟踪标志 1222 |
---|---|---|---|
Output format | 在 SQL Server 错误日志中捕获输出。 | 重点关注死锁中涉及的节点。 每个节点都有一个专用部分,最后一部分描述死锁牺牲品。 | 以不符合 XML 架构定义 (XSD) 架构的类似 XML 格式返回信息。 该格式有三个主要部分。 第一部分声明死锁牺牲品。 第二部分说明死锁所涉及的每个进程; 第三部分说明与跟踪标志 1204 中的节点同义的资源。 |
Identifying attributes |
SPID:<x> ECID:<x>. 标识并行进程时会话 ID 线程。 条目 SPID:<x> ECID:0 (其中,<x> 将替换为 SPID 值)表示主线程。 条目 SPID:<x> ECID:<y> (其中,<x> 将替换为 SPID 值,<y> 大于 0)表示具有相同 SPID 的子线程。BatchID (跟踪标志 1222 的 sbid )。 标识代码执行从中请求或持有锁的批处理。 禁用多重活动结果集 (MARS) 后,BatchID 值为 0。 启用 MARS 后,活动批处理的值为 1 到 n。 如果会话中没有活动批处理,则 BatchID 为 0。Mode 为线程请求、授予或等待的特定资源指定锁的类型。 模式可以为 IS(意向共享)、S(共享)、U(更新)、IX(意向排他)、SIX(共享意向排他)和 X(排他)。Line # (跟踪标志 1222 的 line )。 列出死锁发生时正在执行的当前语句批处理中的行号。Input Buf (跟踪标志 1222 的 inputbuf )。 列出当前批处理中的所有语句。 |
Node 表示死锁链中的条目编号。Lists 锁所有者可以是这些列表的一部分:Grant List 枚举资源的当前所有者。Convert List 枚举试图将锁转换为更高级别的当前所有者。Wait List 枚举对资源的当前新锁请求。Statement Type 说明线程对 DML 语句(SELECT 、INSERT 、UPDATE 或 DELETE )具有权限的类型。Victim Resource Owner 指定 SQL Server 选择作为牺牲品来中断死锁循环的参与线程。 所选线程和所有现有子线程都将终止。Next Branch 表示死锁循环中涉及的来自同一 SPID 的两个或多个子线程。 |
deadlock victim 表示选为死锁牺牲品的任务的物理内存地址(请参阅 sys.dm_os_tasks)。 如果任务为无法解析的死锁,则它可能为 0(零)。 正在回滚的任务不能被选为死锁牺牲品。executionstack 表示发生死锁时正在执行的 Transact-SQL 代码。priority 表示死锁优先级。 在某些情况下,SQL Server 数据库引擎可能在短时间内改变死锁优先级以更好地实现并发。logused 任务使用的日志空间。owner id 可控制请求的事务的 ID。status 任务的状态。 它是以下值之一:- pending 正在等待工作线程。- runnable 已准备好运行,但在等待量子。- running 当前正在计划程序中运行。- suspended 执行已挂起。- done 任务已完成。- spinloop 正在等待自旋锁释放。waitresource 任务需要的资源。waittime 等待资源的时间(毫秒)。schedulerid 与此任务关联的计划程序。 See sys.dm_os_schedulers.hostname 工作站的名称。isolationlevel 当前事务的隔离级别。Xactid 可控制请求的事务的 ID。currentdb 数据库的 ID。lastbatchstarted 客户端进程上次启动批处理执行的时间。lastbatchcompleted 客户端进程上次完成批处理执行的时间。clientoption1 和 clientoption2 在此客户端连接上设置选项。 这是一个位掩码,包含关于通常由 SET 语句控制的选项的信息,例如 SET NOCOUNT 和 SET XACTABORT 。associatedObjectId 表示 HoBT(堆或 B 树)ID。 |
Resource attributes |
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_id DB: 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 的输出。
Important
SQL Profiler 创建跟踪,该跟踪已于 2016 年弃用并由扩展事件替换。 与跟踪相比,扩展事件的性能开销要少得多,并且可配置性要高得多。 请考虑使用扩展事件死锁事件而不是跟踪。
有关死锁事件的详细信息,请参阅 Lock:Deadlock 事件类。 有关运行 SQL Profiler 死锁图的详细信息,请参阅保存死锁图 (SQL Server Profiler)。
扩展事件中 SQL 跟踪事件类具有等效项,请参阅查看与 SQL 跟踪事件类等效的扩展事件。 建议通过 SQL 跟踪使用扩展事件。
Handle deadlocks
SQL Server 数据库引擎实例选择某事务作为死锁牺牲品时,将终止当前批处理,回退事务并将错误消息 1205 返回应用程序。
Your transaction (process ID #52) 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 的错误处理程序允许应用程序处理死锁情况并采取补救措施(例如,自动重新提交死锁中涉及的查询)。 通过自动重新提交查询,用户不必知道发生了死锁。
应用程序在重新提交其查询前应短暂暂停。 这样会给死锁涉及的另一个事务一个机会来完成并释放构成死锁循环一部分的该事务的锁。 这将把重新提交的查询请求其锁时,死锁重新发生的可能性降到最低。
使用 TRY...CATCH 进行处理
可以使用 TRY...CATCH 处理死锁。 1205 死锁牺牲品错误可以被 CATCH
块捕获,并且事务可以回滚,直到线程解锁。
有关详细信息,请参阅处理死锁。
Minimize deadlocks
尽管死锁不能完全避免,但遵守特定的编码约定可以将发生死锁的可能性降至最低。 将死锁减至最少可以增加事务的吞吐量并减少系统开销,因为只有很少的事务:
- 回滚,撤消事务执行的所有工作。
- 由于死锁时回滚而由应用程序重新提交。
若要最大程度减少死锁,请执行以下操作:
- 按同一顺序访问对象。
- 避免事务中的用户交互。
- 保持事务简短并处于一个批处理中。
- 使用较低的隔离级别。
- 使用基于行版本控制的隔离级别。
- 将
READ_COMMITTED_SNAPSHOT
数据库选项设置为 on,使读取提交的事务可以使用行版本控制。 - 使用快照隔离。
- 将
- 使用绑定连接。
按同一顺序访问对象
如果所有并发事务都以相同的顺序访问对象,则死锁不太可能发生。 例如,如果两个并发事务先获取 Supplier
表上的锁,然后获取 Part
表上的锁,则在其中一个事务完成之前,另一个事务将在 Supplier
表上被阻塞。 在第一个事务提交或回滚后,第二个事务继续进行,不会发生死锁。 对所有数据修改使用存储过程可以使访问对象的顺序标准化。
避免事务中的用户交互
避免编写包含用户交互的事务,因为没有用户干预的批处理的运行速度远快于用户必须手动响应查询时的速度(例如回复输入应用程序请求的参数的提示)。 如果一个事务正在等待用户输入,而用户去吃午餐甚至是周末回家了,则用户就耽误了事务的完成。 这将降低系统的吞吐量,因为事务持有的任何锁只有在事务提交或回滚后才会释放。 即使不出现死锁的情况,在占用资源的事务完成之前,访问同一资源的其他事务也会被阻塞。
保持事务简短并处于一个批处理中
在同一数据库中并发执行多个需要长时间运行的事务时通常会发生死锁。 事务的运行时间越长,它持有排他锁或更新锁的时间也就越长,从而会阻塞其他活动并可能导致死锁。
保持事务处于一个批处理中可以最小化事务中的网络通信往返量,减少完成事务和释放锁可能遭遇的延迟。
有关死锁的详细信息,请参阅事务锁定和行版本控制指南。
使用较低的隔离级别
确定事务是否可以在较低的隔离级别上运行。 实现读取提交允许一个事务读取之前读取的、但未由另一个事务修改的数据,而无需等待第一个事务完成。 使用较低的隔离级别(例如已提交读)比使用较高的隔离级别(例如可序列化)持有共享锁的时间更短。 这样就减少了锁争用。
使用基于行版本控制的隔离级别
当 READ_COMMITTED_SNAPSHOT
数据库选项设置为 ON
时,在读取提交隔离级别下运行的事务在读取操作期间使用行版本控制而不是共享锁。
Note
某些应用程序依赖于读取已提交隔离的锁定和阻塞行为。 对于这些应用程序,需要进行一些更改才能启用此选项。
快照隔离还使用行版本控制,在读取操作期间不使用共享锁。 必须将 ALLOW_SNAPSHOT_ISOLATION
数据库选项设置为 ON
,事务才能在快照隔离下运行。
实现这些隔离级别,以尽量减少读写操作之间可能发生的死锁。
使用绑定连接
使用绑定连接,同一应用程序打开的两个或多个连接可以相互协作。 由辅助连接获取的任何锁都会被保存,就像它们是由主连接获取的一样,反之亦然。 因此,他们不会相互阻塞。
停止事务
在死锁方案中,受害者事务会自动停止并回滚。 无需在死锁方案中停止事务。
导致死锁
Note
此示例在AdventureWorksLT2019
时,在具有默认架构和数据的 示例数据库中运行。 要下载此示例,请访问 AdventureWorks 示例数据库。
若要造成死锁,你需要将两个会话连接到 AdventureWorksLT2019
数据库。 我们将这些会话称为会话 A 和会话 B。你只需在 SQL Server Management Studio (SSMS) 中创建两个查询窗口即可创建这两个会话。
在会话 A 中,运行以下 Transact-SQL。 此代码开始一个显式事务,并运行用于更新 SalesLT.Product
表的单个语句。 为此,该事务将在表 的一行上获取SalesLT.Product
,该锁将转换为排他 (X) 锁。 我们将该事务保持打开状态。
BEGIN TRANSACTION;
UPDATE SalesLT.Product
SET SellEndDate = SellEndDate + 1
WHERE Color = 'Red';
现在,在会话 B 中,运行以下 Transact-SQL。 此代码不会显式开始一个事务, 而是在自动提交事务模式下运行。 此语句更新 SalesLT.ProductDescription
表。 该更新会获取一个针对 SalesLT.ProductDescription
表中 72 行的更新 (U) 锁。 查询将联接到其他表,包括 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
中的行(包括被会话 A 锁定的行)使用共享锁。会话 B 将在 SalesLT.Product
上被阻塞。
返回到会话 A。运行以下 Transact-SQL 语句。 这会运行第二个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 中默认启用并处于活动状态。 请考虑下列查询:
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
文件以获得可视死锁图。 死锁图应如下图所示。
优化的锁定和死锁
适用于:Azure SQL 数据库
优化锁定引入了一种不同的锁定机制方法,该方法更改了涉及独占 TID 锁的死锁的报告方式。 在死锁报表 <resource-list>
中的每个资源下,每个 <xactlock>
元素都会报告死锁中每个成员的锁的基础资源和特定信息。
考虑以下启用了优化锁定的示例:
CREATE TABLE t2
(
a INT PRIMARY KEY NOT NULL,
b INT NULL
);
INSERT INTO t2
VALUES (1, 10),
(2, 20),
(3, 30);
GO
以下 Transact-SQL 命令在两个会话中对表 t2
创建死锁:
在会话 1 中:
--session 1
BEGIN TRANSACTION foo;
UPDATE t2
SET b = b + 10
WHERE a = 1;
在会话 2 中:
--session 2:
BEGIN TRANSACTION bar;
UPDATE t2
SET b = b + 10
WHERE a = 2;
在会话 1 中:
--session 1:
UPDATE t2
SET b = b + 100
WHERE a = 2;
在会话 2 中:
--session 2:
UPDATE t2
SET b = b + 20
WHERE a = 1;
这种竞争 UPDATE
语句场景会导致死锁。 在本例中,是一个键锁资源,其中每个会话在自己的 TID 上持有 X 锁,并等待另一个 TID 上的 S 锁,从而导致死锁。 以下作为死锁报告捕获的 XML 包含特定于优化锁定的元素和属性: