Spring 事务失效排查:先确认代理边界,再看传播行为 Spring 事务失效排查先确认代理边界再看传播行为一、事务失效通常不是数据库问题很多线上数据不一致问题排查到最后会发现并不是数据库不可靠而是 Spring 事务根本没有生效。常见现象包括方法抛异常后数据仍然提交内部方法调用没有回滚异步线程里的写操作不受事务控制捕获异常后事务没有标记回滚。这些问题表面分散根因大多和代理边界、异常类型、传播行为和线程上下文有关。排查事务问题时不要一开始就盯数据库隔离级别。第一步应确认当前方法是否真的被 Spring AOP 代理拦截。Transactional只有在代理调用链上才会生效普通对象内部this.method()调用不会经过代理。理解这一点可以少走很多弯路。二、调用链路事务边界在代理对象上创建flowchart TD A[Controller] -- B[Service Proxy] B -- C[TransactionInterceptor] C -- D[Target Service Method] D -- E[Repository] E -- F[Database] D -- G[this 内部调用]在这个链路里事务拦截器只会包住从代理对象进入的方法。如果目标方法内部再用this调用另一个带Transactional的方法后者不会创建新的事务边界。类似地private方法、final方法或者非 Spring Bean 上的注解也不会按预期生效。还有一种隐蔽场景是多数据源。业务方法使用了事务管理器 A但实际写入走了数据源 B对应事务自然管不住。项目中存在多个PlatformTransactionManager时建议显式指定事务管理器并在启动时检查数据源与事务管理器绑定关系。三、代码排查异常捕获会改变回滚语义下面示例看似抛出了业务异常但如果异常被吞掉事务就可能正常提交。Transactional(rollbackFor Exception.class) public void createOrder(OrderCommand command) { orderRepository.save(command.toOrder()); try { inventoryClient.lock(command.skuId(), command.quantity()); } catch (Exception ex) { log.warn(lock inventory failed, ex); throw new OrderCreateException(inventory lock failed, ex); } }如果业务需要捕获异常后继续处理也要明确是否标记回滚。可以重新抛出异常也可以调用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。不过后者会让代码和 Spring 事务强耦合应谨慎使用更推荐通过清晰的业务异常表达失败。默认情况下Spring 只对运行时异常和 Error 回滚。受检异常不会自动回滚除非配置rollbackFor。这在调用外部系统、文件处理和批量导入场景中很常见。团队应约定统一的业务异常体系避免每个方法自行理解回滚规则。四、传播行为不要把 REQUIRED 当成万能默认值REQUIRED是最常见的传播行为表示当前有事务就加入没有就新建。它适合大多数业务写操作但并不适合所有场景。审计日志、操作流水和失败记录有时需要REQUIRES_NEW独立提交否则主事务回滚时排障证据也会消失。批量任务中要特别小心大事务。一个方法处理几万条数据如果全部包在同一个事务里会导致锁持有时间长、undo 日志膨胀、连接占用过久。更合理的方式是按批次拆分事务每批记录独立提交并记录失败项。事务越大回滚代价越高。排查时可以打开事务日志观察事务创建、加入、提交和回滚过程。结合 SQL 日志和 traceId就能判断某次写入是否处在预期事务中。事务问题不适合靠猜必须用日志证明代理是否进入、异常是否抛出、事务是否提交。在复杂调用链中事务边界还可能受到异步线程影响。如果业务方法内启动新线程执行数据库操作该操作不会继承当前事务上下文。类似地使用Async、线程池或消息队列处理的任务都需要独立管理事务。团队应在设计阶段梳理所有写操作路径标注事务边界避免想当然地认为在同一个方法里就一定在同一个事务里。五、总结Spring 事务失效排查应先确认代理边界再看异常类型、传播行为、多数据源和线程上下文。事务不是一个注解就能解决的魔法它是一套调用链规则。把边界画清楚数据一致性问题才有稳定的排查路径。