在两个或多个任务中,如果每个任务锁定了其他任务试图锁定的资源,此时会造成这些任务永久阻塞,从而出现死锁。下图清楚地显示了死锁状态,其中:
- 任务 T1 具有资源 R1 的锁(通过从 R1 指向 T1 的箭头指示),并请求资源 R2 的锁(通过从 T1 指向 R2 的箭头指示)。
- 任务 T2 具有资源 R2 的锁(通过从 R2 指向 T2 的箭头指示),并请求资源 R1 的锁(通过从 T2 指向 R1 的箭头指示)。
- 因为这两个任务都需要有资源可用才能继续,而这两个资源又必须等到其中一个任务继续才会释放出来,所以陷入了死锁状态。
SQL Server 数据库引擎自动检测 SQL Server 中的死锁循环。数据库引擎选择一个会话作为死锁牺牲品,然后终止当前事务(出现错误)来打断死锁。
每个用户会话可能有一个或多个代表它运行的任务,其中每个任务可能获取或等待获取各种资源。以下类型的资源可能会造成阻塞,并最终导致死锁。
- 锁。等待获取资源(如对象、页、行、元数据和应用程序)的锁可能导致死锁。例如,事务 T1 在行 r1 上有共享锁(S 锁)并等待获取行 r2 的排他锁(X 锁)。事务 T2 在行 r2 上有共享锁(S 锁)并等待获取行 r1 的排他锁(X 锁)。这将导致一个锁循环,其中,T1 和 T2 都等待对方释放已锁定的资源。
- 工作线程。排队等待可用工作线程的任务可能导致死锁。如果排队等待的任务拥有阻塞所有工作线程的资源,则将导致死锁。例如,会话 S1 启动事务并获取行 r1 的共享锁(S 锁)后,进入睡眠状态。在所有可用工作线程上运行的活动会话正尝试获取行 r1 的排他锁(X 锁)。因为会话 S1 无法获取工作线程,所以无法提交事务并释放行 r1 的锁。这将导致死锁。
- 内存。当并发请求等待获得内存,而当前的可用内存无法满足其需要时,可能发生死锁。例如,两个并发查询(Q1 和 Q2)作为用户定义函数执行,分别获取 10MB 和 20MB 的内存。如果每个查询需要 30MB 而可用总内存为 20MB,则 Q1 和 Q2 必须等待对方释放内存,这将导致死锁。
- 并行查询执行的相关资源。通常与交换端口关联的处理协调器、发生器或使用者线程至少包含一个不属于并行查询的进程时,可能会相互阻塞,从而导致死锁。此外,当并行查询启动执行时,SQL Server 将根据当前的工作负荷确定并行度或工作线程数。如果系统工作负荷发生意外更改,例如,当新查询开始在服务器中运行或系统用完工作线程时,则可能发生死锁。
- 多个活动的结果集 (MARS) 资源。这些资源用于控制在 MARS 下交叉执行多个活动请求(请参阅批处理执行环境和 MARS)。
- 用户资源。线程等待可能被用户应用程序控制的资源时,该资源将被视为外部资源或用户资源,并将按锁进行处理。
- 会话互斥体。在一个会话中运行的任务是交叉的,意味着在某一给定时间只能在该会话中运行一个任务。任务必须独占访问会话互斥体,才能运行。
- 事务互斥体。在一个事务中运行的所有任务是交叉的,意味着在某一给定时间只能在该事务中运行一个任务。任务必须独占访问事务互斥体,才能运行。
U1: Rs1=Command1.Execute("insert sometable EXEC usp_someproc"); U2: Rs2=Command2.Execute("select colA from sometable");
- 用户资源。线程等待可能被用户应用程序控制的资源时,该资源将被视为外部资源或用户资源,并将按锁进行处理。
上面列出的所有资源均参与数据库引擎死锁检测方案。死锁检测是由锁监视器线程执行的,该线程定期搜索数据库引擎实例的所���任务。以下几点说明了搜索进程:
- 默认时间间隔为 5 秒。
- 如果锁监视器线程查找死锁,根据死锁的频率,死锁检测时间间隔将从 5 秒开始减小,最小为 100 毫秒。
- 如果锁监视器线程停止查找死锁,数据库引擎将两个搜索间的时间间隔增加到 5 秒。
- 如果刚刚检测到死锁,则假定必须等待锁的下一个线程正进入死锁循环。检测到死锁后,第一对锁等待将立即触发死锁搜索,而不是等待下一个死锁检测时间间隔。例如,如果当前时间间隔为 5 秒且刚刚检测到死锁,则下一个锁等待将立即触发死锁检测器。如果锁等待是死锁的一部分,则将会立即检测它,而不是在下一个搜索期间才检测。
通常,数据库引擎仅定期执行死锁检测。因为系统中遇到的死锁数通常很少,定期死锁检测有助于减少系统中死锁检测的开销。
锁监视器对特定线程启动死锁搜索时,会标识线程正在等待的资源。然后,锁监视器查找特定资源的所有者,并递归地继续执行对那些线程的死锁搜索,直到找到一个循环。用这种方式标识的循环形成一个死锁。
检测到死锁后,数据库引擎通过选择其中一个线程作为死锁牺牲品来结束死锁。数据库引擎终止正为线程执行的当前批处理,回滚死锁牺牲品的事务并将 1205 错误返回到应用程序。回滚死锁牺牲品的事务会释放事务持有的所有锁。这将使其他线程的事务解锁,并继续运行。1205 死锁牺牲品错误将有关死锁涉及的线程和资源的信息记录在错误日志中。
默认情况下,数据库引擎选择运行回滚开销最小的事务的会话作为死锁牺牲品。此外,用户也可以使用 SET DEADLOCK_PRIORITY 语句指定死锁情况下会话的优先级。可以将 DEADLOCK_PRIORITY 设置为 LOW、NORMAL 或 HIGH,也可以将其设置为范围(-10 到 10)间的任一整数值。死锁优先级的默认设置为 NORMAL。如果两个会话的死锁优先级不同,则会选择优先级较低的会话作为死锁牺牲品。如果两个会话的死锁优先级相同,则会选择回滚开销最低的事务的会话作为死锁牺牲品。如果死锁循环中会话的死锁优先级和开销都相同,则会随机选择死锁牺牲品。
使用 CLR 时,死锁监视器将自动检测托管过程中访问的同步资源(监视器、读取器/编写器锁和线程联接)的死锁。但是,死锁是通过在已选为死锁牺牲品的过程中引发异常来解决的。因此,请务必理解异常不会自动释放牺牲品当前拥有的资源;必须显式释放资源。用于标识死锁牺牲品的异常与异常行为一样,也会被捕获和解除。
为了查看死锁信息,数据库引擎提供了监视工具,分别为两个跟踪标志以及 SQL Server Profiler 中的死锁图形事件。
跟踪标志 1204 和跟踪标志 1222
发生死锁时,跟踪标志 1204 和跟踪标志 1222 会返回在 SQL Server 2005 错误日志中捕获的信息。跟踪标志 1204 会报告由死锁所涉及的每个节点设置格式的死锁信息。跟踪标志 1222 会设置死锁信息的格式,顺序为先按进程,然后按资源。可以同时启用这两个跟踪标志,以获取同一个死锁事件的两种表示形式。
事件探查器死锁图形事件
这是 SQL Server Profiler中表示死锁所涉及的任务和资源的图形描述的事件。下面的示例显示启用死锁图形事件时 SQL Server Profiler 的输出。
有关运行 SQL Server Profiler 死锁图形的详细信息,请参阅使用 SQL Server Profiler 分析死锁。