Java数据库死锁处理需分三步:精准识别(用异常类型或SQLState而非字符串匹配)、安全重试(抽离事务、幂等操作、Spring Retry配置退避)、主动预防(统一加锁顺序、缩小事务粒度、优化查询)。

Java 中数据库死锁发生时,不能靠“自动捕获并重试”这种模糊逻辑——必须明确识别死锁异常类型、限定重试范围、控制事务生命周期,并配合退避策略。核心不是写个 try-catch 就完事,而是分三步:精准识别、安全重试、避免复发。
精准识别死锁异常,别依赖字符串匹配
不同数据库的死锁错误码和 SQLState 是稳定标识,必须用它们判断,而不是 getMessage().contains("deadlock") 这类不可靠方式:
-
MySQL(Spring 环境):捕获
DeadlockLoserDataAccessException,它是 Spring 对 MySQL 死锁(如 ER_LOCK_DEADLOCK)的封装,无需再解析 SQLState -
Oracle:检查
SQLException.getErrorCode() == 60 && "61000".equals(ex.getSQLState()) -
PostgreSQL / 标准 SQL:统一用
ex.getSQLState().equals("40001")(SERIALIZATION_FAILURE 或 DEADLOCK_DETECTED) -
注意:Spring 的
@Retryable默认不重试受检异常(如 SQLException),需显式配置include = { SQLException.class }并确保异常未被上层吞掉
安全重试必须脱离@Transactional方法体
在 Spring 声明式事务中,@Transactional 方法抛出死锁异常时,事务已在提交阶段回滚,此时方法早已返回,AOP 代理无法拦截重试。正确做法是:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 把核心 DML 操作抽成无事务注解的私有方法,外层方法用
try-catch包裹并手动重试 - 或改用
TransactionTemplate显式控制事务,在每次重试中重新开启事务:txTemplate.execute(status -> { /* 执行DML */ }) - 每次重试前,确认连接可用、事务未标记为
rollbackOnly,且不能复用已失效的 Connection 或 PreparedStatement - 仅允许重试幂等性 DML(如 UPDATE/INSERT with idempotent key),禁止对 SELECT FOR UPDATE、跨服务调用、文件写入等逻辑重试
用 Spring Retry 实现可配置的退避重试
比手写 for-loop + sleep 更可靠,推荐直接使用 spring-retry:
立即学习“Java免费学习笔记(深入)”;
- 启动类加
@EnableRetry - 在目标方法上标注:
@Retryable(value = SQLException.class, include = { DeadlockLoserDataAccessException.class }, maxAttempts = 3, backoff = @Backoff(delay = 100, multiplier = 2)) - 搭配
@Recover提供兜底逻辑,例如记录告警或触发人工干预 - 退避建议:首重试延迟 50–100ms,后续按指数增长(×1.5~2),最多 3 次;第 2 次起可加小范围随机抖动(如 ±30ms),缓解重试风暴
比重试更重要的是预防死锁本身
重试只是容错手段,80% 的死锁源于业务逻辑缺陷:
- 所有跨表更新操作,必须约定统一顺序(如总是先 orders → 再 order_items → 最后 payments)
- 减少事务粒度:避免在事务中做 HTTP 调用、消息发送、复杂计算
- 查询尽量加
FOR UPDATE SKIP LOCKED(MySQL 8.0+/PostgreSQL)或SELECT ... FOR UPDATE NOWAIT(Oracle),主动失败快于等待死锁 - 定期分析数据库死锁日志(MySQL 的
SHOW ENGINE INNODB STATUS,Oracle 的 trace 文件),定位高频冲突 SQL 和表

















