死锁是多个并发事务因循环等待资源被sql server主动终止并报错1205;需通过dbcc traceon(1222,-1)或错误日志获取xml死锁图,比对process的inputbuf和resource-list定位a等b、b等a的闭环依赖;set lock_timeout对死锁无效,真正解决须索引优化、统一访问顺序、拆分事务。

死锁不是存储过程写错了,而是多个并发事务在争夺资源时卡成环——SQL Server 会主动报错 1205 并干掉一个牺牲者,而不是让你等下去。
怎么看死锁图里谁在卡谁
收到 Deadlock encountered 后,别急着改代码,先捞死锁图。启用 DBCC TRACEON(1222, -1),或查 SQL Server 错误日志(sp_readerrorlog),搜 1205 或 deadlock。XML 里盯两个地方:
-
process节点下的inputbuf:显示该会话最后执行的语句,比如EXEC dbo.proc_update_order或直接是UPDATE Orders SET ... -
resource-list里的资源类型:KEY表示某一行被锁,PAGE是数据页,OBJECT是整张表——锁粒度越大越危险 - 重点比对两个
process的inputbuf和它们等待的resource:A 在等 B 锁的行,B 又在等 A 锁的行,链就闭环了
为什么 SET LOCK_TIMEOUT 完全没用
在存储过程开头加 SET LOCK_TIMEOUT 5000 不防死锁,只防普通阻塞(超时抛 1204)。死锁是 SQL Server 在 1–5 毫秒内检测并终止事务,根本没走到超时逻辑。
- 更危险的是把它和重试混用:第一次死锁回滚后,数据状态已变(比如某行被其他事务改了),重试可能引发新冲突或业务错乱
-
LOCK_TIMEOUT只适用于明确知道会被长期锁住的场景,比如带WITH (TABLOCK)的SELECT;对死锁毫无意义
真正有效的三类动作:索引、顺序、拆分
死锁归根结底是锁范围太大 + 锁持有太久 + 访问顺序不一致。必须落到具体操作上:
-
索引优化:让
UPDATE精准锁行,而不是扫全表。给WHERE条件字段建联合索引,顺序按AND出现顺序排;删掉无用的INCLUDE列(尤其是大字段),避免锁升级为页锁 -
统一访问顺序:多表操作时,所有存储过程必须严格按同一顺序访问表(如总是先
Customers→ 再Orders→ 最后OrderItems);注意触发器、外键级联、IN (SELECT ...)子查询也会悄悄引入额外表,必须纳入顺序约束 -
缩短事务时间:把一次更新 1000 行的大事务,改成
WHILE循环 +TOP (50)分片 + 显式COMMIT;把 RPC 调用、日志写入、JSON 序列化等非 DB 操作全部移到COMMIT之后
最容易被忽略的是隐式访问顺序和辅助操作:JOIN 顺序不等于执行顺序,真正起作用的是索引查找路径;日志表写入、临时表创建、链接服务器调用这些“配角”,往往才是死锁真正的导火索。











