准确识别ORA-00060必须用ex.getErrorCode() == 60 && "61000".equals(ex.getSQLState()),禁用getMessage()字符串匹配;重试须限定事务内可恢复DML,配合指数退避、幂等设计及手动事务控制。
java 应用里捕获到 ora-00060,不能靠 getmessage() 里找字符串“deadlock”来判断——这会在线上环境频繁失效。
怎么准确识别 ORA-00060 异常
Oracle JDBC 驱动返回的 SQLException 中,getErrorCode() 恒为 60,getSQLState() 恒为 "61000"。这是唯一稳定、跨版本、不受国际化或日志截断影响的判断依据。
- ✅ 正确写法:
ex.getErrorCode() == 60 && "61000".equals(ex.getSQLState()) - ❌ 错误写法:
ex.getMessage().contains("ORA-00060")或ex.getMessage().contains("deadlock") - ⚠️ 注意:Spring 的
@Retryable默认不重试 checked exception,必须显式配置include = { SQLException.class }
重试逻辑必须限定在事务内可恢复的 DML 场景
死锁发生时,Oracle 已将 victim 会话的整个事务回滚,但你的 Java 线程可能还在执行后续语句。若盲目重试,容易导致重复插入、状态错乱或 Transaction is not active 异常。
- 重试前必须确认事务仍活跃:调用
TransactionStatus.isCompleted()(Spring)或connection.getAutoCommit() == false && !connection.isClosed() - 每次重试前要重新获取连接,不能复用已标记为
rollbackOnly的连接 - 仅允许重试纯 DML(
INSERT/UPDATE/DELETE),禁止对SELECT FOR UPDATE或含外部调用(HTTP/文件)的逻辑重试 - 重试次数建议 ≤ 3 次,第二次起加随机休眠:
Thread.sleep(50 + new Random().nextInt(100))
Spring @Transactional 下如何安全重试
直接在 @Transactional 方法上加 @Retryable 是无效的——死锁异常实际抛出在事务提交阶段(TransactionSynchronization.afterCompletion),此时方法早已返回,AOP 代理无法捕获。
- 方案一:把核心 DML 拆到无事务注解的私有方法中,由带
@Transactional的外层方法手动调用 + try-catch + 重试 - 方案二:改用
TransactionTemplate显式控制事务,在catch块里调用txTemplate.execute(...) - ⚠️ 关键点:重试块内不能嵌套
@Transactional,否则会创建新事务,破坏原子性
比重试更重要的是避免死锁本身
重试只是兜底,80% 的 ORA-00060 来自多模块更新顺序不一致。比如 A 服务先 UPDATE orders 再 UPDATE order_items,B 服务反过来,就必然竞争。
立即学习“Java免费学习笔记(深入)”;
- 所有跨表 DML 必须按主键升序统一执行顺序(如始终先按
order_id升序,再按item_id升序) - 检查
WHERE条件是否命中索引;全表扫描会锁大量无关行,把SELECT FOR UPDATE改成带唯一索引键的精确查询 - 事务中禁用长耗时操作:HTTP 调用、大文件读写、复杂计算都应剥离到事务外
真正棘手的不是怎么重试,而是怎么让重试逻辑几乎 never 被触发——这取决于你能否约束住所有服务对同一组资源的访问路径。


















