死锁经常与正常阻塞混淆。当一个事务请求对另一个事务锁定的资源加锁时,请求加锁的事务会等待直到锁被释放。默认情况下,除非设置了 LOCK_TIMEOUT,否则 SQL Server 事务不会超时。请求事务被阻塞,而不是死锁,因为请求事务没有做任何事情来阻塞持有锁的事务。最终,持有锁的事务将完成并释放锁,然后请求事务将获得锁并继续。死锁几乎可以立即解决,而阻塞理论上可以无限期地持续下去。
死锁可能发生在任何有多个线程的系统中,而不仅仅是关系数据库管理系统,而且可能发生在数据库对象锁以外的资源上。例如,多线程操作系统中的线程可能会获取一个或多个资源,如内存块。如果正在获取的资源当前为另一个线程所有,则第一个线程可能需要等待拥有资源的线程释放目标资源。等待的线程对拥有该特定资源的线程具有依赖性。在 SQL Server 数据库引擎实例中,会话在获取内存或线程等非数据库资源时可能会出现死锁。
当对表进行分区并将 alter table 的 lock_escalation 设置为 auto 时,也可能出现死锁。当 lock_escalation 设置为 auto 时,由于允许 sql server 数据库引擎在 hobt 级别而不是在表级别锁定表分区,因此并发性会提高。但是,当不同事务在表中持有分区锁,并希望在其他事务分区的某个地方获得锁时,就会导致死锁。将 lock_escalation 设置为 table 可以避免这种死锁;不过,这种设置会降低并发性,因为它会迫使分区的大型更新等待表锁。
可能造成死锁的资源
每个用户会话都可能有一个或多个任务在运行,每个任务都可能获取或等待获取资源。以下类型的资源可能会导致阻塞,从而造成死锁。
1.锁
等待获取对象、页、行、元数据和应用程序等资源的锁可能会导致死锁。
2.工作线程
等待可用工作线程的队列任务可能会导致死锁。如果队列任务拥有的资源阻塞了所有工作线程,就会导致死锁。例如,会话 S1 启动事务并获得行 r1 上的共享 (S) 锁,然后进入休眠状态。在所有可用工作线程上运行的活动会话正试图获取 r1 行上的独占 (X) 锁。由于会话 S1 无法获取工作线程,因此它无法提交事务并释放 r1 行上的锁。这就导致了死锁。
3.内存
当并发请求等待的内存授予无法用可用内存满足时,就会出现死锁。例如,两个并发查询 Q1 和 Q2 作为用户定义函数执行,分别获取 10 MB 和 20 MB 内存。如果每个查询需要 30 MB,而可用内存总量为 20 MB,那么 Q1 和 Q2 必须等待对方释放内存,从而导致死锁。
4.并行查询执行相关资源。
与交换端口相关联的协调者、生产者或消费者线程可能会相互阻塞,导致死锁,通常是在包括至少一个不属于并行查询的其他进程时。此外,当并行查询开始执行时,SQL Server 会根据当前的工作量确定并行程度或工作线程的数量。如果系统工作量发生意外变化,例如服务器上开始运行新查询或系统工作线程用完,就可能出现死锁。
5.多活动结果集 (MARS) 资源
这些资源用于控制 MARS 下多个活动请求的交错。
·用户资源。
当线程在等待可能由用户应用程序控制的资源时,该资源会被视为外部或用户资源,并被视为锁。
·会话mutex。
在一个会话中运行的任务是交错的,这意味着在给定时间内,该会话下只能运行一个任务。在任务运行之前,它必须拥有对会话互斥器的独占访问权限。
·事务mutex。
在一个事务中运行的所有任务都是交错的,这意味着在给定时间内,只有一个任务能在事务下运行。在任务运行之前,它必须拥有对事务互斥器的独占访问权限。
任务要在 MARS 下运行,必须获得会话mutex。如果任务在事务中运行,则必须获取事务mutex。这就保证了在给定会话和给定事务中,同一时间只有一个任务处于活动状态。获得所需的mutex后,任务就可以执行了。当任务完成或在请求中途放弃时,它将首先释放事 mutex,然后按获取的相反顺序释放会话mutex。然而,这些资源可能会出现死锁。在下面的伪代码中,用户请求 U1 和用户请求 U2 这两个任务运行在同一个会话中。
U1: Rs1=Command1.Execute("insert sometable EXEC usp_someproc"); U2: Rs2=Command2.Execute("select colA from sometable");
用户请求 U1 执行的存储过程已获得会话mutex。如果存储过程需要很长时间才能执行,SQL Server 数据库引擎会认为存储过程正在等待用户的输入。用户请求 U2 在等待会话mutex时,用户正在等待 U2 的结果集,而 U1 正在等待用户资源。这种死锁状态在逻辑上可表示为
死锁检测
死锁检测由锁监控线程执行,该线程会定期启动对 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 时,死锁监视器会自动检测 managed procedures 中访问的同步资源(死锁器、读写锁和线程连接)的死锁。不过,死锁是通过在被选中为死锁受害者的存储过程中抛出异常来解决的。需要注意的是,异常不会自动释放受害者当前拥有的资源;必须明确释放这些资源。与异常行为一致,用于识别死锁受害者的异常可以被捕获并解除。
死锁信息工具
要查看死锁信息,SQL Server 数据库引擎以 system_health xEvent 会话、两个trace标志和 SQL Profiler 中的死锁图事件的形式提供监控工具。
官方推荐使用死锁扩展事件。
SQL Profiler 会创建trace,但trace已于 2016 年过时,取而代之的是扩展事件。与trace相比,扩展事件的性能开销要小得多,可配置性也高得多。可以考虑使用扩展事件死锁事件来代替trace。
死锁扩展事件
从 SQL Server 2012 (11.x) 开始,在 SQL Trace 或 SQL Profiler 中应使用 xml_deadlock_report 扩展事件 (xEvent) 代替死锁图事件类。
同样从 SQL Server 2012 (11.x) 开始,当发生死锁时,system_health 会话已捕获包含死锁图的所有 xml_deadlock_report xEvent。由于默认启用了 system_health 会话,因此无需配置单独的 xEvent 会话来捕获死锁信息。使用 xml_deadlock_report xEvent 捕捉死锁信息不需要额外的操作。
捕获的死锁图通常有三个不同的节点:
·victim-list:死锁受害者进程标识符。
·进程列表:死锁涉及的所有进程的信息。
·资源列表:死锁涉及的资源信息。
打开 system_health 会话文件或环形缓冲区(ring buffer),如果记录了 xml_deadlock_report xEvent,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;
以下是输出结果集:
标签:事务,Server,会话,死锁,线程,SQL From: https://www.cnblogs.com/abclife/p/18400039