Skip to content

分布式事务

很多人第一次接触分布式事务时,最常见的困惑是:数据库事务不是已经能保证一致性了吗?

问题在于,数据库事务通常更擅长处理:单个数据库、单个事务边界里的操作一致性。

一旦操作跨了多个服务、多个库、多个资源,问题就不再是普通本地事务了。

这篇文章重点讲:

  1. 分布式事务到底是什么
  2. 它为什么会出现
  3. 常见方案在解决什么问题
  4. 它们分别适合什么场景
  5. 为什么很多企业问题最后不是“强上事务”,而是重构流程

1. 分布式事务到底是什么

可以这样理解:一笔业务操作需要跨多个服务、多个库或多个资源一起完成时,如何尽量保证整体状态正确。

例如下单场景可能同时涉及:

  1. 订单服务写订单
  2. 库存服务扣库存
  3. 支付服务记录支付

这时如果只靠单库本地事务,就很难保证这几步天然一起成功或一起失败。


2. 为什么本地事务不够了

在单库里,一条事务边界能包住多条 SQL。

但在分布式系统里,常见情况是:

  1. 不同服务有自己的数据库
  2. 不同服务独立部署、独立提交
  3. 调用之间会有网络失败、超时和重试

所以问题就变成:一个服务提交成功了,另一个服务失败了,系统怎么收场。

这就是分布式事务问题的核心。


3. 一个最直观的例子

假设用户下单:

  1. 订单服务创建订单成功
  2. 库存服务扣库存失败

这时系统就会出现:

  1. 订单状态已经生成
  2. 库存却没扣成功

或者反过来:

  1. 库存扣了
  2. 订单却没创建成功

这两种情况都说明:业务整体没有保持一致。


4. 分布式事务为什么会很难

它难就难在你同时要面对:

  1. 跨服务调用
  2. 跨数据库提交
  3. 网络不可靠
  4. 服务会超时、会重试、会失败

所以它不是“事务范围更大一点”这么简单,而是:原本由单机数据库内核兜住的事情,现在要靠多个独立系统协同完成。


5. 常见分布式事务思路有哪些

企业里常见的思路通常包括:

  1. 强一致型协议
  2. 最终一致性方案
  3. 补偿型事务

5.1 2PC

2PC 是两阶段提交。

它的核心思路是:

  1. 先问所有参与方能不能提交
  2. 都能提交时再统一提交

它更偏强一致,但代价通常较高:

  1. 阻塞明显
  2. 协调者压力大
  3. 故障恢复复杂

5.2 TCC

TCC 通常分为:

  1. Try
  2. Confirm
  3. Cancel

它的核心思路是:先预留资源,再确认提交,失败时执行补偿取消。

它更适合对业务流程有明确控制能力的场景。

5.3 可靠消息最终一致性

它的核心思路是:本地事务先保证本地状态正确,再通过消息把变更可靠地传播给其他服务。

这类方案在企业里非常常见,因为它更现实,也更符合高可用系统习惯。


6. 为什么很多系统更偏向最终一致,而不是强一致

因为强一致的代价通常很高:

  1. 性能差
  2. 可用性差
  3. 阻塞明显
  4. 系统复杂度高

很多互联网业务最终会更倾向:接受短暂不一致,但保证最终收敛。

例如:

  1. 订单创建后积分稍后到账
  2. 库存变更通过消息异步同步
  3. 支付结果稍后驱动订单状态更新

7. TCC 到底适合什么场景

TCC 更适合:

  1. 业务动作清晰可拆
  2. 每一步都能设计 Try / Confirm / Cancel
  3. 资金、库存、额度这类强业务约束场景

例如扣库存时:

  1. Try:先冻结库存
  2. Confirm:真正扣减
  3. Cancel:释放冻结

它的优点是:业务可控性强。

它的代价是:业务侵入性大,开发复杂度高。


8. 可靠消息最终一致性为什么常见

因为很多业务并不要求每一步都在同一时刻绝对一致。

更常见的做法是:

  1. 本地事务先提交核心数据
  2. 再把后续动作通过消息可靠投递出去
  3. 消费方异步处理并重试

例如:

  1. 订单服务本地事务提交订单
  2. 发送“订单已创建”消息
  3. 库存服务消费消息并扣库存

这样做的关键价值是:把大事务拆成可恢复、可重试、可补偿的异步流程。


9. 分布式事务里为什么幂等很重要

因为在分布式环境里,重试几乎不可避免。

一旦发生:

  1. 网络超时
  2. 消息重复投递
  3. 调用方重复重试

就很容易导致同一个动作被执行多次。

所以分布式事务要想真正落地,通常都要配合:幂等设计。

也就是说:同一个请求重复执行,结果不应不断累加出错。


10. 代码层面最常见的设计习惯

虽然分布式事务本身不是靠几行注解解决的,但代码层面有几个习惯非常重要:

  1. 先明确业务主动作和补偿动作
  2. 给操作设计唯一业务单号
  3. 对消息消费和回调处理做好幂等
  4. 把长链路拆成可重试、可恢复的小步骤

例如消费端幂等处理会很常见:

java
public void onOrderCreated(OrderCreatedEvent event) {
    if (processed(event.getOrderId())) {
        return;
    }
    deductStock(event.getProductId(), event.getCount());
    markProcessed(event.getOrderId());
}

11. 企业里什么时候不该硬上分布式事务

这是非常关键的一点。

很多问题如果一开始就试图用“大而全的分布式事务框架”兜住,往往会把系统拖得更复杂。

更好的问题应该是:

  1. 这个流程是否能拆成主流程和异步流程
  2. 哪一步必须强一致,哪一步可以最终一致
  3. 是否能通过补偿和幂等降低耦合

很多时候,真正有效的做法不是“找最强事务方案”,而是:重新设计业务边界。


12. 工程上最容易混的几个边界

12.1 误区一:分布式事务就是把本地事务范围放大

不是。

跨服务后,问题的本质已经变成协调、补偿和恢复。

12.2 误区二:2PC 一定最正确,所以最好

它理论上更强,但代价在很多业务里无法接受。

12.3 误区三:最终一致就是可以不管失败

不是。

最终一致要求的是:有明确的补偿、重试、幂等和状态收敛机制。


13. 一句话总结

分布式事务这条线,本质上是在解决跨服务、跨资源的一致性问题;而企业里真正重要的,不是迷信某个事务模式,而是判断哪些地方必须强一致、哪些地方可以最终一致,并把补偿、幂等和恢复机制一起设计好。

基于 VitePress 构建的个人技术笔记。