在SQL Server存储过程中处理死锁(错误号1205)时,有一个常被忽略的关键点:TRY CATCH实际上无法捕获该错误。死锁一旦发生,执行上下文会直接被终止,CATCH块连执行的机会都没有。因此,重试逻辑必须放置在最外层的批处理中,将整个事务(BEGIN TRAN/EXEC/COMMIT)包裹起来,显式判断ERROR_NUMBER()=1205,配合WAITFOR DELAY延时,并限制重试次数。下面逐一详细说明。

SQL Server 存储过程里不能用 TRY CATCH 捕获 1205 死锁错误
死锁发生后,SQL Server会直接终止当前执行上下文,BEGIN TRY块根本来不及跳转到CATCH块。无论你在存储过程中编写多少层嵌套,只要错误号是1205,控制权都不会进入CATCH。常见的表现包括:应用层仍收到Transaction (Process ID XX) was deadlocked错误;CATCH中的WAITFOR DELAY完全没有执行;在触发器或嵌套过程中放置重试逻辑也毫无效果。
重试必须由最外层批处理控制,且包裹整个事务
真正能够捕获并执行重试的位置,是调用该存储过程的T-SQL批处理(例如SSMS查询窗口、SQLCMD脚本或应用程序拼接后发送的完整语句块)。关键不在于“在哪写重试”,而在于“重试什么”:
- 必须将
BEGIN TRANSACTION、EXEC YourStoredProcedure、COMMIT TRANSACTION全部包裹在TRY块内 - 单独对某条
UPDATE语句增加TRY CATCH并重试,会导致@@TRANCOUNT混乱和事务状态污染 ERROR_NUMBER()必须显式判断是否等于1205,其他错误(如约束冲突、超时)不应重试WAITFOR DELAY '00:00:00.05'是底线,太短会引发重试风暴;超过3次后必须THROW原始错误
MySQL 存储过程可用 DECLARE EXIT HANDLER 实现有限重试
MySQL支持在存储过程中用DECLARE EXIT HANDLER FOR 1213, 1205捕获死锁和锁超时,但有硬性限制:
- 外部不能已经开启事务,否则
START TRANSACTION会报错ERROR 1305 (42000): SA VEPOINT does not exist - 必须显式
ROLLBACK后再调用DO SLEEP(0.01),不能用WAITFOR(那是SQL Server语法) - 重试逻辑要包裹整个事务块,而非单条语句;示例中用
@retry := @retry + 1计数,IF @retry < 3控制上限
Oracle 存储过程靠 SA VEPOINT + LOOP 实现可控重试
Oracle没有TRY CATCH,但可以使用SA VEPOINT配合EXCEPTION WHEN OTHERS来回退到干净状态:
- 每次循环开始前设置
SA VEPOINT sp_start,失败后执行ROLLBACK TO sp_start,避免状态累积 - 禁止在循环内执行
COMMIT;所有COMMIT必须放在成功退出循环之后 - 优先捕获具体异常码(如
ORA-03113、ORA-03114),而不是仅写WHEN OTHERS DBMS_SCHEDULER.CREATE_JOB的max_failures不是过程内重试——那是任务级调度,每次都是全新会话,与事务重试无关
重试机制最容易忽略的一点,是错误地将“失败后再次执行”视为“事务内重试”。实际上,数据库层面真正的重试,永远依赖于上层对整个事务边界的重新发起,而不是存储过程体内的循环或触发器补救。
