oracle存储过程必须用savepoint+loop实现重试,因无try catch;需每次循环设savepoint、失败后rollback to、禁止循环内commit、捕获具体异常码(如ora-00060)、加退避与计数防死循环,并避免将select for update、日志写入、自治事务等放入重试块。

Oracle PL/SQL 里不能靠 TRY CATCH 实现事务内重试,必须用 SAVEPOINT + LOOP 显式控制回退点和重试边界。
为什么不能用 EXCEPTION WHEN OTHERS 捕获死锁后直接重试
ORA-00060 死锁发生时,当前语句会立即失败并抛出异常,但事务仍处于活动状态;如果在 EXCEPTION 块里不做 ROLLBACK TO SAVEPOINT 就直接重试,会导致锁未释放、状态污染、甚至 ORA-00068 错误。更危险的是,WHEN OTHERS 会掩盖真正需要告警的错误(如 ORA-01403、ORA-00942),把数据库故障变成静默失败。
- 只捕获具体错误码:用
EXCEPTION WHEN OTHERS THEN IF SQLCODE = -60 THEN ...,不依赖SQLERRM字符串匹配 - 每次循环开始前必须设
SAVEPOINT sp_start,失败后统一ROLLBACK TO sp_start - 禁止在循环体内
COMMIT—— 所有提交必须放在成功退出循环之后
带计数和退避的重试 LOOP 怎么写才安全
PL/SQL 没有内置重试机制,WHILE 循环必须手动管理变量更新、超限保护和等待逻辑。漏掉任何一环都可能引发死循环或重试风暴。
- 声明固定最大尝试次数:
max_attempts CONSTANT PLS_INTEGER := 3; - 循环变量
attempts必须在每次迭代末尾显式递增:attempts := attempts + 1; - 重试前加退避:
DBMS_LOCK.SLEEP(POWER(2, attempts) * 0.01);(单位秒,避免用WAITFOR,那是 SQL Server 语法) - 退出条件要覆盖所有失败路径:
EXIT WHEN attempts > max_attempts OR success_flag = TRUE;
哪些操作绝对不能放进重试 LOOP
不是所有 DML 都适合重试。盲目包裹 SELECT、DDL 或跨事务操作,会放大并发问题或导致数据不一致。
-
SELECT FOR UPDATE NOWAIT报 ORA-00054 是业务信号,不是可恢复错误 —— 应返回“资源已被占用”,而非重试 -
INSERT INTO log_table日志记录不能和主业务共用一个事务重试块,否则日志可能重复写入 - 调用自治事务(
PRAGMA AUTONOMOUS_TRANSACTION)的函数不能放进去,它会隐式提交,破坏外层事务一致性 - 涉及序列
NEXTVAL的赋值(如l_id := seq.NEXTVAL)必须移出 LOOP,否则每次重试都消耗新号
最容易被忽略的是:重试 LOOP 内没有监控手段。一旦某次执行卡在 DBMS_LOCK.SLEEP 或远程调用上,DBA 在 V$SESSION 里只能看到 “PL/SQL LOCK TIMER”,无法定位是哪个逻辑分支卡住。上线前务必加 DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS 打点。











