SQL Server 存储过程内TRY CATCH无法捕获死锁(1205),因死锁时事务已被强制终止,CATCH不执行;重试必须置于外部批处理中,用WHILE+TRY CATCH控制,且存储过程应专注索引优化、访问顺序统一和缩短事务锁持有时间。

SQL Server 存储过程中无法在 TRY CATCH 内捕获死锁(错误号 1205),因为死锁发生时事务已被引擎强制终止,CATCH 块根本不会执行;真正可行的重试必须放在存储过程调用方的外部批处理中,由 WHILE 循环 + TRY CATCH 主动控制。
为什么存储过程内部的 TRY CATCH 对死锁无效
死锁不是普通运行时错误,而是 SQL Server 在毫秒级检测到循环等待后,主动选择一个会话作为牺牲者并立即终止其事务上下文。此时:
-
TRY CATCH不会触发 —— 语句执行被中断,不进入CATCH块 - 触发器、嵌套存储过程、
EXEC调用中的TRY CATCH同样失效 - 你写的重试逻辑(比如
WAITFOR DELAY或ROLLBACK)根本不会运行 - MySQL 更严格:触发器内禁止
START TRANSACTION和ROLLBACK,直接报ERROR 1305
正确的重试位置:外部批处理包裹 DML 调用
唯一可控、可落地的重试点,是发起原始 INSERT/UPDATE/DELETE 的上层 T-SQL 批处理(比如应用发来的单条语句,或 SSMS 中手动执行的脚本)。关键结构如下:
DECLARE @retry INT = 0, @maxRetry INT = 3; WHILE @retry <p>要点:</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/ai/3498" title="AI Photos"><img src="https://img.php.cn/upload/ai_manual/001/246/273/178599433085305.png" alt="AI Photos" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/ai/3498" title="AI Photos" class="overflowclass">AI Photos</a> <p class="overflowclass">AI Photos是一款AI图像与设计工具,AI图片艺术美化。</p> </div> <a rel="nofollow" href="/ai/3498" title="AI Photos" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div>
- 重试逻辑包裹的是对存储过程的调用,不是存储过程内部的某条语句
-
ERROR_NUMBER() = 1205是唯一应重试的死锁标识,别用WHEN OTHERS模糊兜底 -
WAITFOR DELAY时间建议从'00:00:00.05'起步,避免重试风暴 - 超过 3 次仍失败,必须
THROW原始错误,让应用层决定降级或告警
存储过程自身该做什么(而非重试)
把重试甩给上游后,存储过程要专注做三件事来降低死锁概率:
- 确保所有
UPDATE/DELETE的WHERE条件命中索引 —— 检查执行计划是否走INDEX SEEK,避免全表扫描升级为页锁或表锁 - 多表操作时严格按固定顺序访问(如总是先
Customers→ 再Orders→ 最后OrderItems),连外键级联和IN (SELECT ...)都要纳入顺序约束 - 把非 DB 操作(如日志写入、HTTP 调用、JSON 序列化)全部移到
COMMIT之后,缩短事务持有锁的时间 - 避免在事务中使用
SELECT WITH (UPDLOCK, HOLDLOCK)等人为扩大锁范围的操作,除非有明确且验证过的业务需求
真正容易被忽略的点是:死锁不是“代码没写好”,而是并发下资源申请顺序的冲突;重试只是掩盖症状,索引优化和访问顺序统一才是根治手段。一旦发现重试频繁生效,说明底层设计已暴露风险,该停掉重试去查 sys.dm_exec_requests 和死锁图了。










