set lock_timeout 对死锁完全无效,因其仅控制阻塞等待时长(报错1204),而死锁由sql server在1–5毫秒内主动检测并终止事务(报错1205);重试必须由外部调用方实现,存储过程内try/catch无法捕获1205错误。

SET LOCK_TIMEOUT 对死锁完全无效,别再放存储过程开头了
SET LOCK_TIMEOUT 只控制语句在被其他事务阻塞时最多等多久(报错 1204),而死锁是 SQL Server 引擎在 1–5 毫秒内主动检测并终止事务(报错 1205)——它根本没机会触发超时。你在存储过程开头写 SET LOCK_TIMEOUT 5000,对死锁毫无作用。
常见错误现象包括:日志里反复出现 1205 错误,但 LOCK_TIMEOUT 设置像摆设;或者 sp_who2 显示会话状态为 sleeping,实际早已被引擎杀掉,只是客户端还没收到通知。
- 该设置只对单条语句生效,且必须在语句执行前设置(比如
UPDATE前) - 值为
-1是默认无限等待;0表示不等待、冲突立即失败 - 它对 CPU 密集型操作(如大排序、递归 CTE)完全无效
- 在 Azure SQL 或 Fabric 中,部分版本可能忽略该设置(如 Synapse 仅解析不执行)
重试逻辑必须由外部批处理包裹,存储过程内部无法捕获 1205
SQL Server 遇到 1205 死锁错误时,会直接终止当前执行上下文,BEGIN TRY 块根本不会进入 CATCH。你在存储过程里写再多层 TRY/CATCH,都抓不到这个错误。
真正可行的重试结构是:由调用方(比如应用层或 SQL Agent 作业)用显式事务包裹整个存储过程调用,并在外部判断错误号。
一款AI开发辅助工具,主要用于从 AI 编程会话日志(Clawdbot、Claude Code、Codex)中提取对话记录。该功能用于在用户要求导出提示词历史、会话日志或 `.jsonl` 格式的会话文件时使用,适合需要提升相关任务效率的用户。
- 重试前必须加
WAITFOR DELAY '00:00:00.1',避免瞬间重试加剧锁竞争 - 延迟建议从 100ms 起步,逐次倍增(最多 3 次),防止雪崩
- 外层事务需确保可安全回滚;若存储过程内已
BEGIN TRAN,重试前要先IF @@TRANCOUNT > 0 ROLLBACK - 只对
ERROR_NUMBER() IN (1205, 1222)这类可重试错误重试,其他错误(如约束冲突、语法错)应直接抛出
真正有效的死锁防控点:锁序、持锁时间、索引覆盖
死锁本质是多个事务以不同顺序申请同一组资源。所有“超时+重试”都是事后补救,真正要做的,是让死锁几乎不可能发生。
- 统一表访问顺序:所有涉及
orders、order_items、customers的存储过程,强制按此物理顺序写UPDATE或JOIN,哪怕某次只更新其中两张表 - 拆小事务:避免单次
UPDATE TOP (1000),改用TOP (50)分批 + 显式COMMIT,缩短锁持有窗口 - 删掉乱用的锁提示:除非明确走唯一索引查找,否则
SELECT ... WITH (UPDLOCK, HOLDLOCK)容易锁整页甚至整表 - 确认索引覆盖:用执行计划检查是否走
Index Seek;避免varchar(max)或冗余INCLUDE列导致行太宽,触发锁升级
应用层 CommandTimeout 和存储过程内 LOCK_TIMEOUT 要分清职责
Connection Timeout=30 控制的是连上 SQL Server 的耗时;CommandTimeout(如 ADO.NET 的 SqlCommand.CommandTimeout)才是真正的执行超时开关,它触发的是客户端强制断开,SQL Server 端会收到 ERROR 0 并回滚当前批处理。
容易踩的坑是:存储过程里设了 SET LOCK_TIMEOUT 3000,但应用层 CommandTimeout 设成 10 秒,结果锁等 3 秒失败后重试,第 2 次又卡住,第 3 次才超时——用户感知是“越来越慢”,而不是“快速失败”。
-
LOCK_TIMEOUT是语句级阻塞等待上限,适合防长事务卡住某条 UPDATE -
CommandTimeout是客户端级执行总耗时上限,适合兜底防整个存储过程失控 - 两者共存时,
CommandTimeout应大于最大可能的重试耗时(比如 3 次 × (3s + 0.1s + 0.2s) ≈ 10s) - 真正容易被忽略的点是:死锁日志里标记的“牺牲品”,往往不是最慢的语句,而是最先申请锁、又恰好形成环的那个事务——优化得看锁资源申请路径是否收敛,不是只盯执行时间










