在数据库操作中,死锁是一个不可避免的问题,尤其是在高并发环境下。SQL Server通过内置的死锁检测机制来解决这一问题。当检测到死锁时,SQL Server会终止其中一个事务,通常是资源占用较少的那个。频繁的死锁会严重影响应用程序的性能和可扩展性。
死锁是一种特殊的阻塞场景,其中两个或多个线程相互阻塞,以至于没有一个可以继续执行。考虑两个事务T1和T2,它们分别使用资源R1和R2。T1持有R1的独占锁,而T2持有R2的独占锁。如果在执行过程中,T1需要R2的独占锁,同时T2需要R1的独占锁,那么T1和T2就会陷入死锁。网络上有很多文章解释死锁以及如何处理它们。这里,将尝试解释如何实时跟踪死锁及其解决方案;当然,一旦知道了原因,解决方案就很简单了。
讨论的应用程序是一个简单的远程服务器,服务于大约300个并发用户。该应用程序编写得很好,遵循了大多数最佳实践。启用SQL Server跟踪后,发现了大量的死锁和事务失败。要启用标志1204,请使用DBCC TRACEON (1222, -1)。SQL Server 2005引入了一个新的、改进过的T1024版本。如果使用的是SQL Server 2005,请使用T1222标志。以下是T1024标志打印的死锁信息(为了清晰起见,已截断):
// 任何遇到的死锁 .... 打印死锁信息
2007-09-13 16:54:47.15 spid4
2007-09-13 16:54:47.15 spid4 Wait-for graph
2007-09-13 16:54:47.15 spid4
2007-09-13 16:54:47.15 spid4 Node:1
2007-09-13 16:54:47.15 spid4 KEY: 7:645577338:1 (12014f0bec4f)
...
将尽量简化分析。死锁信息中的每个节点代表涉及的资源。在每个节点下,有三个部分:KEY、GrantList和Requested By。
KEY后面跟着资源在系统表(systables和sysindexes)中出现的ID。资源ID KEY有三个部分:{KEY : DatabaseID : TableID: IndexType},其中IndexType为1表示聚集索引,大于1表示非聚集索引。
GrantList顾名思义,列出了所有已授予资源某种锁的进程。它列出了持有锁的SPID、锁模式和正在执行的SQL。
这部分列出了等待获取资源锁的SPID。SPID 51正在等待获取同一资源的Range-Insert-Null锁。Range-Insert-Null锁是在实际插入之前测试可插入性所需的。
两个SPID持有的Range-S-S锁表明应用程序正在使用序列化隔离模式,因为当在序列化隔离模式下使用SELECT时,会使用这种锁定模式。序列化隔离是可能的最高隔离级别,它消除了幻读并保证了可重复读。可重复读保证要求在事务范围内持有RangeS-S锁。