PL/SQL中不能用AUTONOMOUS_TRANSACTION捕获ORA-00060后重试,因其脱离原事务上下文导致数据不一致;安全做法是EXCEPTION WHEN OTHERS中判SQLCODE = -60后显式ROLLBACK,并最多重试2次,第二次前加随机休眠。

Oracle PL/SQL 中不能用 AUTONOMOUS_TRANSACTION 捕获死锁后重试
触发器里遇到 ORA-00060 时,有人想用 AUTONOMOUS_TRANSACTION 开一个独立事务去“兜底重试”,这是危险操作。自治事务会脱离原事务上下文,导致数据不一致、约束校验失效,甚至掩盖真正的并发问题。
Oracle 在检测到死锁时,已主动回滚被选为 victim 的那个事务——但触发器本身仍在执行流中,若此时开启自治事务再执行 DML,等于在“已失败的事务”上叠加新变更,违反原子性。
实操建议:
- 不要在触发器内捕获
ORA-00060后做任何 DML 重试 - 若必须响应死锁,只允许记录日志(如插入
deadlock_log表),且该表操作需走自治事务 + 异常忽略(EXCEPTION WHEN OTHERS THEN NULL;) - 真正重试逻辑应上移到应用层,由 Java 或其他服务控制事务边界和退避策略
PL/SQL 中安全捕获 ORA-00060 的唯一可行方式:EXCEPTION WHEN OTHERS + 显式 ROLLBACK
触发器或存储过程中,只有在顶层异常块中捕获 OTHERS,再通过 SQLCODE 判断是否为 -60,才是 Oracle 官方支持的路径。注意:SQLCODE 返回的是负值,所以判断条件是 SQLCODE = -60,不是 60。
关键限制:
- 不能只
CONTINUE或跳过,必须先ROLLBACK当前事务状态(否则下次 DML 可能报ORA-01086: savepoint 'xxx' never established) - 不能只重试某条 UPDATE,而要重新执行整个业务逻辑块(含所有前置校验、计算、多表更新)
- 重试次数必须硬编码限制(最多 2 次),且第二次前加随机休眠:
DBMS_LOCK.SLEEP(0.05 + DBMS_RANDOM.VALUE(0, 0.1));
示例片段:
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -60 THEN
ROLLBACK;
IF v_retry_count < 2 THEN
v_retry_count := v_retry_count + 1;
DBMS_LOCK.SLEEP(POWER(2, v_retry_count) * 0.01);
GOTO retry_block;
ELSE
RAISE_APPLICATION_ERROR(-20001, 'Deadlock retry exhausted');
END IF;
ELSE
RAISE;
END IF;为什么触发器不适合承担死锁重试责任
根本原因在于触发器的执行时机和事务耦合度太高。它没有能力判断当前事务是否已被 Oracle 标记为 rollback-only,也无法安全地重建连接或重置会话状态。
常见误判场景:
- 触发器里执行
SELECT FOR UPDATE后发生死锁 → Oracle 回滚整个语句,但触发器后续仍试图更新其他字段,报ORA-00900: invalid SQL statement - 外键级联触发器中嵌套调用另一个含 UPDATE 的过程 → 死锁后重试导致父表/子表状态错位
- 触发器中调用
UTL_HTTP等耗时操作 → 锁持有时间不可控,放大死锁概率而非缓解
结论很直接:把重试逻辑放在触发器里,等于把刹车系统装在轮胎上——位置错了,越用力越失控。
真正有效的死锁应对,得从应用层和 SQL 设计入手
触发器能做的极其有限,而大多数 ORA-00060 都源于可预防的设计缺陷。比如两个模块分别按 order_id 和 item_id 更新,只要统一改成先按 LEAST(order_id, item_id) 升序处理,80% 的死锁就消失了。
落地建议:
- 所有多表 DML,强制在 WHERE 条件中使用唯一索引键(避免全表扫描锁行)
- UPDATE 语句禁用
WHERE column LIKE '%xxx%',改用精确匹配或函数索引 - 事务中禁止混合数据库操作与 HTTP 调用、文件 IO、复杂计算
- Java 层用
TransactionTemplate显式控制事务,并在catch块中检查ex.getErrorCode() == 60 && ex.getSQLState().equals("61000")
最易被忽略的一点:死锁日志里的 rowid(如 AAAR3sAAFAAAABgAAA)和 session_id 比你自己的 SQL 更值得盯紧——它们指向真实冲突资源,而不是你以为的问题源头。


















