数据一致性全靠它!Spring 事务传播行为没搞懂=大坑

原文来自于:zha-ge.cn/java/120

数据一致性全靠它!Spring 事务传播行为没搞懂=大坑

昨天和同事聊到一个订单系统的故事,我终于明白为什么"传播行为"这件小事会把数据搞得像过山车。表面上看,事务只是为了让数据要么全成功要么全失败,但在 Spring 里,它还会把我们救命的"边界"给挤来挤去。有时候你以为自己把两件事打包在同一个事务里,结果还没写完就崩了,数据就开始分崩离析。于是我把自己踩过的坑整理成这篇小记,愿意当你们的"救火绳"。

在一个典型的微服务场景里,往往会有一组相关的操作需要原子性:下单、扣库存、扣钱、发消息。这些步骤如果放在不同的事务里,哪怕只有一个环节失败,整条链路就会不一致。最常见的坑,是把库存扣减放在一个"独立事务"里(比如 Propagation.REQUIRES_NEW),而把下单和支付放在外层同一个事务里。结果就是:下单回滚了,库存却已经提交了,账也可能不一致。嗯,这就是传说中的"数据对不上号"的大坑。

踩坑瞬间

  • 场景要点:父事务包含多个子调用,子调用中有一个用到了 REQUIRES_NEW 的独立事务。父事务抛出异常后,子事务已经提交,导致最终数据不一致。

  • 关键代码节选(简化版,聚焦点在传播行为):

java 复制代码
@Service
public class OrderService {
  @Autowired private InventoryService inventoryService;
  @Autowired private PaymentService paymentService;

  @Transactional
  public void placeOrder(OrderDTO o) {
     inventoryService.reserve(o.getItemId(), o.getQty()); // 子调用在独立事务中执行
     paymentService.charge(o.getAmount()); // 仍在父事务内
     // ... 业务逻辑
     if (someError) throw new RuntimeException(); // 回滚父事务
  }
}

@Service
public class InventoryService {
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void reserve(Long itemId, int qty) {
     // 扣减库存,然后提交独立事务
  }
}

这段看起来像是"让两段逻辑都走完"的设计,实际效果却是:如果某处后续异常导致父事务回滚,库存的扣减已经在自己的独立事务里提交,永远不会被回滚。数据就成了两份不一致的账。

经验启示

  • 了解回滚边界:传播行为不是"好用就行",它其实在告诉你哪部分应该随父事务一起回滚,哪部分可以独立提交。明确哪些操作必须原子性地一起成功/失败。

  • 避免对相关业务使用 REQUIRES_NEW:如果你的下单、库存、支付是一个完整的业务流程,尽量让它们在同一个事务内完成,避免中间出现独立提交导致的最终不一致。

  • 需要独立提交时,也要有补偿机制:把"独立事务"放在真正独立的业务边界,如异步消息、外部系统调用后再做补偿,而不是直接让它们嵌在同一个调用链里随父事务的生死起伏。

  • 配置回滚规则要清晰:默认情况下运行时会回滚 RuntimeException,但如果你有自定义的异常类型,记得通过 rollbackFor 指定哪些异常会触发回滚,以免误判。

  • 小结:在设计事务边界时,先画好"数据一致性 needs"图,再决定传播行为。否则你写再多注解,也难免遇到那种在开发时"看起来对"的方案,上线就变成"大坑"。

写在最后

如果你现在还在犹豫传播行为到底怎么选,给自己一个简单的口径:要对数据强一致的部分,用同一个事务把相关调用绑死;要对外部系统或长时间任务进行解耦,使用明确的独立边界并配好补偿逻辑。别让一个小小的传播选择,把后续几十行代码的正确性全都打碎。愿你我都能在复杂的分布式场景里,靠这点"边界感"活成稳稳的老程序员。

相关推荐
ltl5 小时前
【大模型基础设施工程】22:大模型网关
后端
a8a3026 小时前
Laravel8.x新特性全解析
java·spring boot·后端
前端百草阁6 小时前
【吃透 Promise】从基础到面试高频(手写 + 输出题 + 原理)
okhttp·面试·职场和发展
白露与泡影6 小时前
Spring Boot 完整流程
java·spring boot·后端
Mr.Rice.Fool6 小时前
rust面试经验1
后端·面试·职场和发展·rust
薛定猫AI7 小时前
【深度解析】Gemma Chat 本地 AI 编程 Agent:Electron + MLX + 开源模型的离线 Vibe Coding 实战
javascript·人工智能·electron
全栈前端老曹7 小时前
【前端地图】多地图平台适配方案——高德、百度、腾讯、Google Maps SDK 差异对比、封装统一地图接口
前端·javascript·百度·dubbo·wgs84·gcj-02·bd09
笑虾7 小时前
Win10 修改注册表 让鼠标悬停PNG上时 tip 始终显示分辨率
开发语言·javascript·ecmascript
禧西7 小时前
面试准备——agent和大模型
面试·职场和发展