数据一致性全靠它!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"图,再决定传播行为。否则你写再多注解,也难免遇到那种在开发时"看起来对"的方案,上线就变成"大坑"。

写在最后

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

相关推荐
葫芦和十三4 小时前
图解 MongoDB 23|两地三中心:跨可用区部署怎么扛机房故障
后端·mongodb·agent
勇哥java实战分享6 小时前
PaddleOCR 太慢?我换成 RapidOCR 后,速度直接起飞
后端
山河木马10 小时前
矩阵专题3-怎么创建投影矩阵(uProjectionMatrix)
javascript·webgl·计算机图形学
苏三说技术11 小时前
LangChain4j 和 LangGraph4j,哪个更好?
后端
ServBay12 小时前
7 个AI开发中真正用得上的 MCP Server,配合Claude Code食用效果更佳
后端·claude·mcp
泯泷12 小时前
第 2 篇:设计第一套字节码:Opcode、Instruction 与 Constant Pool
前端·javascript·安全
妙码生花12 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十五):优化细节、网络请求封装
前端·后端·ai编程
泯泷12 小时前
第 1 篇:从 1 + 2 开始:亲手写出第一台 JSVM
前端·javascript·安全
用户67570498850213 小时前
Go 语言里判断字符串为空,90% 的人都写错了!
后端·go