TRY…CATCH无法捕获死锁(错误1205),因死锁发生时事务已被强制终止,控制权不进入CATCH块;重试逻辑必须置于调用存储过程的外部批处理中。

TRY…CATCH 在 SQL Server 存储过程中无法捕获死锁(错误号 1205)。这不是写法问题,而是引擎行为决定的——死锁发生时,事务已被强制终止,控制权根本不会进入 CATCH 块。
TRY…CATCH 对死锁无效的根本原因
- 死锁不是普通运行时错误,而是 SQL Server 毫秒级检测到循环等待后,主动选一个会话作为牺牲者并立即终止其事务上下文
- 此时:
-
BEGIN TRY中的后续语句不再执行 -
END TRY到BEGIN CATCH的跳转不发生 - 你在存储过程里写的
WAITFOR DELAY、ROLLBACK、重试逻辑全被跳过
-
- 这种行为在触发器、嵌套存储过程、
EXEC调用中完全一致,无一例外
常见错误现象包括:
- 应用层仍收到
Transaction (Process ID XX) was deadlocked - 存储过程内
PRINT或日志记录没输出 -
@@TRANCOUNT在CATCH中为 0,说明事务早已被清空
死锁重试必须放在外部批处理中
唯一可行、可控、可落地的位置,是调用该存储过程的上层 T-SQL 批处理(比如 SSMS 查询窗口、SQLCMD 脚本、或应用拼接后发送的完整语句块),结构必须是:
-
BEGIN TRANSACTION -
EXEC your_procedure -
COMMIT TRANSACTION
全部包裹在同一个TRY块内
示例关键结构:
DECLARE @retry INT = 0, @maxRetry INT = 3;
WHILE @retry < @maxRetry
BEGIN
BEGIN TRY
BEGIN TRANSACTION;
EXEC dbo.update_order_status @order_id = 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必须显式判断,不能只靠@@ERROR或泛化异常处理 -
WAITFOR DELAY时间建议从'00:00:00.05'起步,太短易引发重试风暴 - 超过 3 次必须
THROW,避免无限循环掩盖真实问题 - 不要用
EXEC('KILL '+@spid),防止注入;改用sys.sp_executesql
MySQL 存储过程中能用 DECLARE EXIT HANDLER 吗?
可以,但限制极严:
- 外部不能已开启事务,否则
START TRANSACTION报错ERROR 1305(SAVEPOINT does not exist) - 只能用
DO SLEEP(0.01),WAITFOR是 SQL Server 语法,MySQL 不识别 - 重试逻辑必须在存储过程内部完成,且不能依赖外部状态
也就是说:MySQL 的方案和 SQL Server 完全不兼容,不能照搬。
真正容易被忽略的一点是:死锁不是靠“加重试”解决的,而是靠减少它发生的概率。索引缺失、访问顺序不一致、事务时间过长、在触发器里做远程调用……这些才是根源。重试只是兜底手段,不是替代优化的捷径。

















