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 < @maxRetry
BEGIN
BEGIN TRY
BEGIN TRANSACTION;
EXEC dbo.your_stored_procedure @param1 = 123;
COMMIT TRANSACTION;
BREAK; -- 成功则退出循环
END TRY
BEGIN CATCH
IF ERROR_NUMBER() = 1205
BEGIN
SET @retry += 1;
IF @retry < @maxRetry
WAITFOR DELAY '00:00:00.1';
-- ROLLBACK 由 CATCH 自动触发,无需显式写
END
ELSE
BEGIN
ROLLBACK TRANSACTION;
THROW; -- 其他错误原样抛出
END
END CATCH
END要点:
- 重试逻辑包裹的是对存储过程的调用,不是存储过程内部的某条语句
-
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 和死锁图了。

















