sqlserver死锁详解
开启死锁监控
- 开启监视死锁的开关,让 SQL Server 遇到死锁时,在 errorlog 文件中打印出死锁的详细信息
--.将死锁信息写入错误日志
DBCC TRACEON (1222, -1);
一个完整的死锁过程
- 第1步,创建演示表及数据
USE demo
GO
IF OBJECT_ID('Bookings') IS NOT NULL DROP TABLE Bookings;
IF OBJECT_ID('Payments') IS NOT NULL DROP TABLE Payments;
CREATE TABLE Bookings (ID INT PRIMARY KEY, Status VARCHAR(10), Amount DECIMAL(10,2));
CREATE TABLE Payments (ID INT PRIMARY KEY, BookingID INT, Amount DECIMAL(10,2));
INSERT INTO Bookings VALUES (1, 'Pending', 100.00);
INSERT INTO Payments VALUES (101, 1, 100.00);
- 第2步,执行如下代码(会话 A)
-- ==================== 会话 A ====================
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN TRANSACTION;
UPDATE Bookings SET Status = 'Confirmed' WHERE ID = 1;
WAITFOR DELAY '00:00:05';
UPDATE Payments SET Amount = 95.00 WHERE BookingID = 1;
COMMIT TRANSACTION;
- 第3步,5 秒内执行如下代码(会话 B)
-- ==================== 会话 B ====================
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN TRANSACTION;
UPDATE Payments SET Amount = 90.00 WHERE ID = 101;
WAITFOR DELAY '00:00:05';
UPDATE Bookings SET Amount = 95.00 WHERE ID = 1;
COMMIT TRANSACTION;
- 第4步,等待约 5-10 秒,SQL Server 会检测到死锁,其中一个会话提交成功,另一个被终止(通常是执行成本较低的那个)并返回以下错误:
注:本例中会话 A(Process ID 63)提交成功,会话 B(Process ID 65)被终止
Msg 1205, Level 13, State 51, Line 6
Transaction (Process ID 65) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
消息 1205,级别 13,状态 51,第 6 行
事务(进程 ID 65)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务。
这就是经典的交叉资源争夺导致的死锁场景。当两个会话分别持有了对方下一步需要更新的资源锁,并且相互等待对方释放锁时,就形成了死锁。
解读:具体就是,会话 A 先锁定了 Bookings 表中的行,然后延迟5秒;会话 B 先锁定了 Payments 表中的行,也延迟5秒。之后会话 A 尝试更新 Payments 表,但该行已被会话 B 锁定,于是会话 A 进入等待状态;同时会话 B 尝试更新 Bookings 表,但该行已被会话 A 锁定,于是会话 B 也进入等待状态。此时两个会话互相等待对方释放锁,谁也无法继续执行,SQL Server 的死锁检测机制会主动选择一个会话作为“牺牲品”回滚其事务,从而打破僵局。
死锁日志分析
- 查看最近30天发生的死锁次数
--.查看最近30天发生的死锁次数
DECLARE @start_time datetime
SET @start_time = GETDATE()-30
EXEC sys.xp_readerrorlog 0,1,N'deadlock victim',Null,@start_time,NULL,'DESC'
注:以上脚本的查询返回大致如下:
| LogDate | ProcessInfo | Text |
|---|---|---|
| 2026-05-20 09:23:44.520 | spid21s | deadlock victim=process22a5f245867 |
| 2026-05-20 10:46:18.850 | spid22s | deadlock victim=process22a5f115848 |
| 2026-05-20 15:06:15.390 | spid24s | deadlock victim=process22a59d5a4e8 |
- 执行如下查询,获取 ERRORLOG 的完整路径
DECLARE @param nvarchar(512)
EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\Setup', N'SQLDataRoot', @param OUTPUT
SELECT @param+'\Log\ERRORLOG'
- 打开 ERRORLOG 可以看到完整的死锁记录,主要分为3部分:
--.第1部分 deadlock-list 死锁牺牲进程
2026-05-20 15:06:15.39 spid24s deadlock-list
2026-05-20 15:06:15.39 spid24s deadlock victim=process22a59d5a4e8
--.第2部分 process-list 进程信息, inputbuf 下面是具体执行的语句
2026-05-20 15:06:15.39 spid24s process-list
2026-05-20 15:06:15.39 spid24s process id=process22a59d5a4e8 taskpriority=0 logused=144 waitresource=KEY: 5:72057594047430656 (8194443284a0) waittime=2250 ownerId=104630 transactionname=user_transaction lasttranstarted=2026-05-20T15:06:08.127 XDES=0x22a6a18c428 lockMode=X schedulerid=1 kpid=7272 status=suspended spid=65 sbid=0 ecid=0 priority=0 trancount=2 lastbatchstarted=2026-05-20T15:06:08.127 lastbatchcompleted=2026-05-20T10:46:18.860 lastattention=1900-01-01T00:00:00.860 clientapp=Microsoft SQL Server Management Studio - 查询 hostname=WIN104 hostpid=10184 loginname=WIN104\Administrator isolationlevel=read committed (2) xactid=104630 currentdb=5 currentdbname=demo lockTimeout=4294967295 clientoption1=671090784 clientoption2=390200
2026-05-20 15:06:15.39 spid24s executionStack
2026-05-20 15:06:15.39 spid24s frame procname=adhoc line=6 stmtstart=56 stmtend=154 sqlhandle=0x02000000015a2d170908a795af29293dfee79ff33f28e6c70000000000000000000000000000000000000000
2026-05-20 15:06:15.39 spid24s unknown
2026-05-20 15:06:15.39 spid24s frame procname=adhoc line=6 stmtstart=410 stmtend=502 sqlhandle=0x02000000aab6e717678c406146f4a7363e591e2badf381350000000000000000000000000000000000000000
2026-05-20 15:06:15.39 spid24s unknown
2026-05-20 15:06:15.39 spid24s inputbuf
2026-05-20 15:06:15.39 spid24s -- ==================== 会话 B ====================
2026-05-20 15:06:15.39 spid24s SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
2026-05-20 15:06:15.39 spid24s BEGIN TRANSACTION;
2026-05-20 15:06:15.39 spid24s UPDATE Payments SET Amount = 90.00 WHERE ID = 101;
2026-05-20 15:06:15.39 spid24s WAITFOR DELAY '00:00:05';
2026-05-20 15:06:15.39 spid24s UPDATE Bookings SET Amount = 95.00 WHERE ID = 1;
2026-05-20 15:06:15.39 spid24s COMMIT TRANSACTION;
2026-05-20 15:06:15.39 spid24s process id=process22a5f115848 taskpriority=0 logused=144 waitresource=KEY: 5:72057594047496192 (6a526fd2904a) waittime=3514 ownerId=104628 transactionname=user_transaction lasttranstarted=2026-05-20T15:06:06.877 XDES=0x22a6a1a8428 lockMode=U schedulerid=1 kpid=5516 status=suspended spid=63 sbid=0 ecid=0 priority=0 trancount=2 lastbatchstarted=2026-05-20T15:06:06.877 lastbatchcompleted=2026-05-20T10:46:18.857 lastattention=1900-01-01T00:00:00.857 clientapp=Microsoft SQL Server Management Studio - 查询 hostname=WIN104 hostpid=10184 loginname=WIN104\Administrator isolationlevel=read committed (2) xactid=104628 currentdb=5 currentdbname=demo lockTimeout=4294967295 clientoption1=671090784 clientoption2=390200
2026-05-20 15:06:15.39 spid24s executionStack
2026-05-20 15:06:15.39 spid24s frame procname=adhoc line=6 stmtstart=56 stmtend=168 sqlhandle=0x02000000d49f2010facbdd9666dcdc4b8c0982319022c3420000000000000000000000000000000000000000
2026-05-20 15:06:15.39 spid24s unknown
2026-05-20 15:06:15.39 spid24s frame procname=adhoc line=6 stmtstart=418 stmtend=524 sqlhandle=0x02000000eef7790465246309b22364f331176c1eee6674160000000000000000000000000000000000000000
2026-05-20 15:06:15.39 spid24s unknown
2026-05-20 15:06:15.39 spid24s inputbuf
2026-05-20 15:06:15.39 spid24s -- ==================== 会话 A ====================
2026-05-20 15:06:15.39 spid24s SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
2026-05-20 15:06:15.39 spid24s BEGIN TRANSACTION;
2026-05-20 15:06:15.39 spid24s UPDATE Bookings SET Status = 'Confirmed' WHERE ID = 1;
2026-05-20 15:06:15.39 spid24s WAITFOR DELAY '00:00:05';
2026-05-20 15:06:15.39 spid24s UPDATE Payments SET Amount = 95.00 WHERE BookingID = 1;
2026-05-20 15:06:15.39 spid24s COMMIT TRANSACTION;
--.第3部分 resource-list 资源信息
2026-05-20 15:06:15.39 spid24s resource-list
2026-05-20 15:06:15.39 spid24s keylock hobtid=72057594047430656 dbid=5 objectname=demo.dbo.Bookings indexname=PK__Bookings__3214EC27BEAFDE54 id=lock22a5ad90e00 mode=X associatedObjectId=72057594047430656
2026-05-20 15:06:15.39 spid24s owner-list
2026-05-20 15:06:15.39 spid24s owner id=process22a5f115848 mode=X
2026-05-20 15:06:15.39 spid24s waiter-list
2026-05-20 15:06:15.39 spid24s waiter id=process22a59d5a4e8 mode=X requestType=wait
2026-05-20 15:06:15.39 spid24s keylock hobtid=72057594047496192 dbid=5 objectname=demo.dbo.Payments indexname=PK__Payments__3214EC27030A8F7F id=lock22a5ad90d80 mode=X associatedObjectId=72057594047496192
2026-05-20 15:06:15.39 spid24s owner-list
2026-05-20 15:06:15.39 spid24s owner id=process22a59d5a4e8 mode=X
2026-05-20 15:06:15.39 spid24s waiter-list
2026-05-20 15:06:15.39 spid24s waiter id=process22a5f115848 mode=U requestType=wait
- 对以上信息进行分类展示如下:
| 参与者 | process22a59d5a4e8 | process22a5f115848 |
|---|---|---|
| SPID | spid=65(会话B) | spid=63(会话A) |
| 牺牲者 | 是(deadlock victim=process22a59d5a4e8) | 否 |
| 进程信息\连接背景 | clientapp=Microsoft SSMS - Query hostname=WIN104 hostpid=10184 loginname=WIN104\Administrator |
clientapp=Microsoft SSMS - Query hostname=WIN104 hostpid=10184 loginname=WIN104\Administrator |
| 进程信息\当前数据库 | currentdb=5 currentdbname=demo | currentdb=5 currentdbname=demo |
| 进程信息\正在申请的资源 | waitresource=KEY: 5:72057594047430656 | waitresource=KEY: 5:72057594047496192 |
| 进程信息\正在申请的锁模式 | lockMode=X | lockMode=U |
| 进程信息\lockTimeout | lockTimeout=4294967295 | lockTimeout=4294967295 |
| 进程信息\开启了几层事务 | trancount=2 | trancount=2 |
| 进程信息\事务隔离级别 | read committed (2) | read committed (2) |
| 进程信息\运行的语句 | 会话 B 中的脚本 | 会话 A 中的脚本 |
| 资源信息\资源类型 | keylock(若为ridlock表示无聚集索引是个堆表) | keylock |
| 资源信息\具体资源 | hobtid=72057594047430656 dbid=5 objectname=demo.dbo.Bookings indexname= PK__Bookings__3214EC27BEAFDE54 |
hobtid=72057594047496192 dbid=5 objectname=demo.dbo.Payments indexname= PK__Payments__3214EC27030A8F7F |
| 资源信息\持有资源进程/类型 | process22a5f115848 (SPID 63) / Mode:X | process22a59d5a4e8 (SPID 65)/ Mode:X |
| 资源信息\等待资源进程/类型 | process22a59d5a4e8 (SPID 65) / Mode:X | process22a5f115848 (SPID 63) / Mode:U |
--.查询死锁报告中的资源信息
select name,database_id,compatibility_level from sys.databases where database_id = 5
select * from sys.objects where object_id in (338100245,370100359)
select * from sys.partitions where partition_id in (72057594047430656,72057594047496192)
解决死锁的方法
下面介绍几种常见的解决死锁的方法,这些方法,各有利弊,各有其使用的场合。
- 1、优化设计,降低事务的长短和复杂度,尽量返回少的结果集
解读:返回的数据越多,需要的锁也就越多,发生死锁的概率越高。而事务越简单,就越不容易发生阻塞或死锁,尽量避免在一个事务里做很多事情。
- 2、调整索引,以调整执行计划,减少锁的申请数目,从而降低死锁发生的概率
解读:如果死锁的双方不会申请对方要申请的锁,那么死锁也就不会发生。如果能引导SQL Server使用Seek的执行计划,只读取要返回的数据,那么申请的锁数量会大大降低,死锁的几率也会减少。推荐此法,既能提高语句执行的效率,又消除了死锁。但如果双方都要修改或返回同样的数据,那么再优化索引,可能也没有用处。
- 3、使用“nolock”参数,让SELECT语句不要申请S锁,从而消除死锁
解读:此法解决死锁立竿见影、很受欢迎,但是它的缺点是很明显的,“nolock”意味着 SELECT 语句将能接受脏读,用户要理解它的副作用,非必要不推荐。另外,它只能解决有S锁参与的死锁问题,如果死锁发生在U锁或者X锁上,nolock是不起作用的
- 4、升级锁的粒度,将死锁转化成一个阻塞问题
解读:死锁是因为双方都申请到了一个资源(比如同一个page上的不同RID),同时又要申请对方的资源。此时使用“PagLock” 参数直接申请Page级别的锁(只能有一个人得到锁资源),而另一个人会被阻塞住(什么资源都申请不到),此时发生的是阻塞,而不是死锁。
注意:此法虽然不会降低事务隔离级别,但是语句更容易被阻塞住,最终的并发度难免受到影响,比较“粗鲁”也不推荐。
- 5、使用 Snapshot 隔离级别
解读:如果死锁里有S锁参与,可以把事务隔离级别从缺省的 Read Committed 改成 Snapshot(加到语句开头),对缓解此类死锁也会有很好的帮助。set transaction isolation level SNAPSHOT
- 6、设置死锁超时参数为合理范围
解读:数据库使用锁超时来避免让用户等待时间过长,默认-1表示没有超时期限(即无限期等待)。建议 SET LOCK_TIMEOUT 300 超过300毫秒自动放弃本次操作,避免进程悬挂
