SQL Server存储过程中TRY/CATCH无法捕获1205死锁错误,因其由引擎直接终止执行上下文;必须由外部批处理包裹EXEC调用,配合WHILE循环、ERROR_NUMBER()=1205判断、WAITFOR DELAY延时及最多3次重试来实现可靠重试。

TRY...CATCH 在存储过程内部对死锁(错误 1205)完全无效——SQL Server 引擎检测到死锁后会立即终止整个执行上下文,不进入 CATCH 块。
为什么存储过程里的 TRY...CATCH 捕获不到 1205
- 死锁不是“运行时错误”,而是引擎级强制中断:事务被选为牺牲品后,所有局部变量、游标、临时表状态全部丢失,
BEGIN TRY所在的执行栈直接销毁。 -
ERROR_NUMBER()、ERROR_MESSAGE()等函数在存储过程内部根本没机会被调用。 - 常见误操作:
- 在存储过程开头写
SET XACT_ABORT ON,以为能“兜住”死锁 → 实际无影响 - 在
CATCH里写WAITFOR DELAY '00:00:00.1'再重试 → 这行代码永远不会执行 - 把重试逻辑封装进
GOTO retry_label→ 语法报错,跨TRY/CATCH边界不被允许
- 在存储过程开头写
正确捕获点:调用方批处理必须包裹重试逻辑
唯一可控的重试入口,在外部 T-SQL 批处理中(如 SSMS 查询窗口、SQL Agent 作业步骤、或应用拼接的完整语句):
- 必须用
WHILE循环 +EXEC调用目标存储过程 - 每次执行后立刻检查
ERROR_NUMBER() = 1205 - 不要捕获所有错误统一重试,否则可能掩盖真实问题(比如主键冲突、权限不足)
示例结构:
DECLARE @retry INT = 0;
WHILE @retry < 3
BEGIN
BEGIN TRY
EXEC dbo.usp_update_order @order_id = 123;
BREAK; -- 成功则退出循环
END TRY
BEGIN CATCH
IF ERROR_NUMBER() = 1205
BEGIN
SET @retry += 1;
WAITFOR DELAY '00:00:00.05'; -- 避免重试风暴
CONTINUE;
END
ELSE
THROW; -- 其他错误直接抛出
END CATCH
END注意:ROLLBACK TRANSACTION 不需要显式写——SQL Server 已自动回滚;再写会触发 “The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION”。
应用层重试比 SQL 层更可靠
- 存储过程无法保留上下文,而应用代码(C#、Java、Python)可以:
- 控制重试间隔(指数退避)
- 记录每次失败的参数与时间戳
- 在重试前校验业务状态(比如订单是否已被取消)
- 避免在 SQL 层做嵌套
EXEC或递归调用,容易放大锁持有时间 - 如果必须用 SQL 层重试,确保参数值不变(如自增 ID、
GETDATE()结果),否则可能引发数据不一致
真正难的不是写重试代码,而是确认:这次失败真的是死锁,而不是因索引缺失导致全表扫描锁升级,或者事务里混入了外部依赖(如链接服务器)拖长了持锁时间。

















