SqlServer死锁问题的监控与解决方法 In 巴西队世界杯名单 @2026-06-14 18:52:17

一、死锁到底是个什么鬼

咱们先来打个比方。想象两个人在狭窄的走廊里迎面相遇,都坚持让对方先走,结果谁都不让谁,就这么僵持住了——这就是死锁的生动写照。在SqlServer里,当两个或多个事务互相等待对方释放锁资源时,就会发生这种"堵车"现象。

举个典型的死锁场景:

事务A锁定了表1,然后请求表2的锁

事务B同时锁定了表2,却请求表1的锁

两个事务就像两个固执的人,大眼瞪小眼谁也不让步

-- 事务1执行顺序

BEGIN TRAN

UPDATE 商品表 SET 库存=库存-1 WHERE 商品ID=1 -- 获取商品1的排他锁

WAITFOR DELAY '00:00:05' -- 模拟业务处理耗时

UPDATE 订单表 SET 状态='已付款' WHERE 订单ID=100 -- 尝试获取订单表的锁

COMMIT TRAN

-- 事务2执行顺序

BEGIN TRAN

UPDATE 订单表 SET 备注='加急' WHERE 订单ID=100 -- 获取订单表的排他锁

WAITFOR DELAY '00:00:03' -- 模拟业务处理耗时

UPDATE 商品表 SET 库存=库存+1 WHERE 商品ID=1 -- 尝试获取商品表的锁

COMMIT TRAN

二、如何发现死锁的蛛丝马迹

SqlServer其实挺贴心的,给我们准备了几种监控死锁的工具:

最快捷的方式就是查看SqlServer错误日志,死锁发生时系统会自动记录

-- 查询最近的死锁信息

EXEC sp_readerrorlog 0, 1, 'deadlock'

使用扩展事件(XEvent)实时监控,就像给数据库装了个行车记录仪

-- 创建死锁监控会话

CREATE EVENT SESSION [Deadlock_Monitor] ON SERVER

ADD EVENT sqlserver.xml_deadlock_report

ADD TARGET package0.event_file(SET filename=N'Deadlock_Monitor')

GO

-- 启动监控

ALTER EVENT SESSION [Deadlock_Monitor] ON SERVER STATE = START

GO

老牌工具SQL Profiler虽然已经退役,但在某些老版本上还是可以凑合用

-- 使用Trace Flag捕获死锁图

DBCC TRACEON (1222, -1) -- 将死锁信息输出到错误日志

DBCC TRACEON (1204, -1) -- 另一种死锁记录格式

三、破解死锁的十八般武艺

遇到死锁别慌张,咱们有全套解决方案:

方案1:调整事务隔离级别

就像调节座椅高度一样,合适的隔离级别能减少锁冲突

-- 使用读已提交快照,减少阻塞

ALTER DATABASE 你的数据库

SET ALLOW_SNAPSHOT_ISOLATION ON

ALTER DATABASE 你的数据库

SET READ_COMMITTED_SNAPSHOT ON

方案2:统一访问顺序

让所有事务按照固定顺序访问表,就像交通规则靠右行驶

-- 修改事务代码,确保总是先访问商品表再访问订单表

CREATE PROCEDURE 下单流程

AS

BEGIN

BEGIN TRAN

-- 总是先操作商品表

UPDATE 商品表...

-- 然后操作订单表

UPDATE 订单表...

COMMIT TRAN

END

方案3:添加索引减少锁范围

就像把大马路划分成多个车道

-- 为常用查询条件添加索引

CREATE INDEX IX_订单表_状态 ON 订单表(状态)

INCLUDE (订单金额, 客户ID)

-- 使用覆盖索引减少锁升级

CREATE INDEX IX_商品表_库存 ON 商品表(库存)

INCLUDE (商品名称, 价格)

方案4:设置死锁优先级

让不太重要的事务主动让路

-- 设置事务优先级

BEGIN TRAN

SET DEADLOCK_PRIORITY LOW -- 本事务优先级较低

-- 业务逻辑...

COMMIT TRAN

四、实战中容易踩的坑

过度依赖NOLOCK提示:虽然能减少阻塞,但可能读到脏数据

-- 危险操作:可能读到未提交的数据

SELECT * FROM 订单表 WITH(NOLOCK) WHERE 订单ID=100

忽略锁升级:当锁数量超过阈值时,SqlServer会自动升级为表锁

-- 监控锁升级事件

DBCC TRACEON (1211, 1224, -1) -- 禁用锁升级(谨慎使用)

长事务问题:事务持续时间越长,死锁风险越高

-- 错误示范:包含用户交互的长事务

BEGIN TRAN

UPDATE 商品表...

-- 等待用户确认...(千万别这么做!)

COMMIT TRAN

忽略死锁重试机制:有时候自动重试就能解决问题

// C#代码示例:死锁重试逻辑

int retryCount = 0;

while(retryCount < 3)

{

try

{

using(var tran = connection.BeginTransaction())

{

// 执行SQL操作

tran.Commit();

break;

}

}

catch(SqlException ex) when (ex.Number == 1205) // 死锁错误码

{

retryCount++;

Thread.Sleep(100 * retryCount);

}

}

五、从预防到治理的全套方案

预防阶段:

定期检查缺失索引

分析执行计划中的警告

使用Database Tuning Advisor获取优化建议

监控阶段:

配置实时告警

建立基线性能指标

定期检查锁等待统计

-- 查询当前锁等待情况

SELECT

t.text AS 执行语句,

wt.wait_type AS 等待类型,

wt.wait_duration_ms AS 等待时长,

wt.resource_description AS 资源描述

FROM sys.dm_os_waiting_tasks wt

INNER JOIN sys.dm_exec_requests r ON wt.session_id = r.session_id

OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) t

WHERE wt.wait_type LIKE 'LCK%'

治理阶段:

优化事务设计

重构问题查询

考虑应用层缓存

应急处理:

使用KILL命令终止阻塞源头

临时调整隔离级别

启用快照隔离

-- 终止阻塞会话

SELECT 阻塞会话ID = blocking_session_id, 被阻塞会话ID = session_id

FROM sys.dm_exec_requests

WHERE blocking_session_id != 0

KILL 55 -- 终止指定会话

六、总结与最佳实践

经过这一番折腾,咱们可以得出几个金科玉律:

事务要短小精悍,别搞马拉松式长事务

访问顺序要统一,就像进出电梯先出后进

索引不是越多越好,但要确保关键查询有合适索引

监控要常态化,别等用户投诉才发现问题

适当使用乐观并发控制,特别是读多写少的场景

最后记住,死锁不可怕,可怕的是对死锁视而不见。只要掌握正确的工具和方法,就能让数据库运行得像高速公路一样畅通无阻。

龙溪儿童遥控直升机遥控迷你玩具鲨鱼直升机带发光二极管手势传感器飞行红外电动直升机玩具
好烟油推荐最佳烟油清单