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