Skip to content

Spring 事务出现未回滚、UnexpectedRollbackException、死锁或数据不一致时怎么排查?

题型:问题排查 · 先确认实际事务边界,再区分应用异常、数据库冲突和跨系统副作用。

排查顺序

  1. 确认现象与范围:记录请求 ID、业务键、应用版本、异常栈、SQL 和事务时间线,区分数据库未回滚、外部副作用未回滚、内层标记回滚导致外层提交失败,以及并发覆盖造成的数据错误。
  2. 线上止损:暂停产生错误数据的入口或高风险消费者,限制自动重试,保护现场日志和数据库死锁信息。若涉及金额、库存、权限等关键数据,先冻结相关业务键并核对影响范围。
  3. 验证事务是否真正生效:检查调用是否经过 Spring 代理、Bean 是否由容器管理、方法与代理模式是否可拦截、事务管理器和数据源是否匹配,以及异步或新线程是否已经离开原事务上下文。
  4. 核对回滚语义:确认异常类型、rollbackFor、异常是否被捕获吞掉、内层事务是否设置 rollback-only,以及传播行为是否创建了预期之外的独立提交。
  5. 分析数据库并发:结合死锁图、锁等待、SQL 顺序、索引和隔离级别,还原参与事务及其资源依赖。对乐观锁失败检查版本条件,对重复写检查唯一约束和幂等键。
  6. 检查事务外副作用:消息、缓存、搜索索引和远程调用不能随数据库自动回滚。根据业务键判断结果是成功、失败还是未知,再通过 Outbox、补偿任务、状态机或人工对账修复。
  7. 永久修复与验证:明确事务边界和异常规范,统一资源访问顺序,补充幂等、唯一约束、有限重试和一致性任务;用并发测试、故障注入和提交点宕机测试验证。
  8. 复盘监控:监控死锁、回滚率、连接等待、Outbox 积压、补偿失败和业务对账差异,并让日志能关联事务、消息和业务键。

可重试性判断

  • 死锁、部分序列化冲突和瞬时连接故障通常可在新事务中有限重试,但必须有退避、截止时间和幂等保障。
  • 唯一约束、参数、权限和业务校验失败通常不可重试。
  • 提交响应丢失、远程写超时和消息发送超时属于结果未知,应先查询或对账,不能直接重复执行副作用。

易错点

  • 数据库回滚不代表已经发送的消息、HTTP 请求或缓存写入也会回滚。
  • UnexpectedRollbackException 往往是内层已标记 rollback-only、外层仍尝试提交的结果,不应只在外层捕获后忽略。
  • 死锁重试只能提高恢复能力,永久修复仍要处理访问顺序、索引、热点和事务时长。

知识关系

从当前问题继续深入,或者回到提出这个问题的知识入口。