分布式事务这个词大家都不陌生,但我发现一个挺普遍的现象:很多人其实没搞清楚自己的业务到底存不存在分布式事务问题,要么该用没用,要么不该用的地方硬上了一套复杂方案。

所以今天这篇文章,先把「怎么判断分布式事务」这件事讲清楚,再聊解决方案怎么选。

先给个书面定义垫一下:分布式事务,是指在分布式系统中,跨越多个数据节点或应用服务的事务操作,需要保证所有参与方的操作要么全部提交,要么全部回滚。说白了,就是把单机事务的 ACID 保证,搬到了多个独立节点都要协同的场景下。


什么情况下才算分布式事务问题

先说最核心的一句话:只要你的事务跨越了「单一服务、单一资源、单一物理节点」这三条边界中的任意一条,就存在分布式事务问题。

这三条边界,可以理解为判断分布式事务问题的充要条件。一条都没跨?那就是本地事务,让数据库自己保证 ACID 就够了。哪怕跨了一条,你就得开始考虑分布式协调了。

我们一条一条来看。

第一条:服务边界。

只要你这个业务逻辑需要多个服务协作完成,就突破了服务边界。典型的例子:用户下单,A 服务创建订单,然后 RPC 调用 B 服务扣减库存。哪怕 A 和 B 用的是同一个数据库,这里也有分布式事务问题。

很多人在这里会有误解,觉得"共用数据库"就等于没有分布式事务。但共用数据库只能说明你的资源边界没有突破,服务边界突破了该协调还是要协调,因为 A 和 B 拿的是两个不同的数据库连接,根本没办法靠本地事务来保证原子性。

第二条:资源边界。

事务操作的资源不止一种类型,就突破了资源边界。比如订单创建之后还要写 Redis 缓存、发 MQ 消息,这里就同时涉及了数据库、Redis 和消息队列三种不同的资源管理器。这三个东西没有统一的事务协调机制,自然就出了分布式事务问题。

第三条:物理节点边界。

即便是同一个服务、同一类资源(比如都是 MySQL),但数据分布在不同物理实例上,也突破了物理节点边界。分库分表是最典型的场景:订单表在库 1,库存表在库 2,一个事务要同时写这两个库,数据库本身的事务机制就管不到了。


现场快速判断三连问

上面这三条边界,落地到实际开发中,可以简化成三个问题依次问自己:

  • 这个事务需要多个服务共同完成吗?需要,分布式事务。
  • 不需要的话,操作了多种类型的资源吗?操作了,分布式事务。
  • 都没有的话,操作的资源分布在多个物理节点吗?是的,分布式事务。

三个都是否,那就踏踏实实用本地事务,别过度设计。

我再举几个实际场景帮你对号入座。

  • A 服务只是创建订单,只动了一个数据库,没有跨服务调用,三问全否,本地事务;
  • A 服务创建订单后调 B 服务扣库存,哪怕共用同一个库,第一问就是"是",分布式事务;
  • A 服务创建订单后发 MQ 消息,第二问就是"是",分布式事务;
  • A 服务在分库场景下同时操作两个库,第三问就是"是",分布式事务。

还有一个常见误区要单独说一下:同一个数据库、同一个服务里的多表操作,比如转账时同时扣 A 账户、增 B 账户,这压根不是分布式事务,是最普通的本地事务,数据库自己保证 ACID 就行。另外并发问题也不是分布式事务问题,多个线程同时改同一行数据,那是隔离级别和锁的问题,两码事。


确认有问题之后,怎么选方案

确认存在分布式事务问题之后,下一步是选方案。

业界主流的分布式事务方案,大体上分两类:一类保证强一致性,代表是基于 XA 协议的两阶段提交(2PC);另一类保证最终一致性,包括 TCC、本地消息表、事务消息、最大努力通知等。

你有没有发现,这个分类其实就对应了 CAP 里的 C 和 A 的取舍——强一致性方案选 CP,最终一致性方案选 AP,鱼和熊掌不可兼得。

如果你的业务对一致性要求极高,不能接受任何中间态,比如核心库存扣减和订单创建必须同时成功或同时回滚,那就考虑 2PC/3PC 或者 Seata 的 XA 模式。但代价是可用性会有损,而且对数据库有要求,性能也相对差一些。

如果你的业务能接受短暂不一致,只要最终能对齐,那最终一致性方案是更主流的选择,也是大多数互联网场景下的实际做法。

这里细说几个常见方案的适用场景和代价:

TCC(Try-Confirm-Cancel)灵活性最强,能精确控制每个步骤,但业务侵入性也最大,三个阶段都要你自己实现,代码量不少,维护成本高。适合对一致性要求较高、又不想走 XA 的场景,比如金融类资金操作。

本地消息表和事务消息,本质上是用消息队列来驱动最终一致性。本地消息表是把消息写到本地库里,再异步投递;事务消息(比如 RocketMQ 的事务消息)则是利用 MQ 本身的事务能力。这类方案实现相对简单,但依赖 MQ,要考虑 MQ 的部署和维护成本,也不是所有 MQ 都支持事务消息,这点要注意。另外,如果业务量特别大,消息堆积可能导致一致性保障不及时,高并发场景要评估清楚。

最大努力通知是最"轻量"的一种,通常用在对实时性要求不高的非核心链路,比如订单状态变更后通知第三方系统,能收到最好,收不到就多重试几次,没有严格的回滚机制。

Seata 是目前业界比较成熟的分布式事务框架,基于 AT、TCC、SAGA、XA 四种模式给出了完整实现,可以按需选用,整体接入成本比自己造轮子低很多,是工程落地的首选之一。

后面我也会出系列文章来分析一下各种分布式事务方案到底怎么落地的。


怎么选,一句话概括

说到这里,选方案的逻辑其实很清晰了。

一致性要求极高 + 可用性可以稍微牺牲,选 XA/2PC;需要强一致但又要保持灵活,选 TCC;能接受最终一致、依赖 MQ 没问题,选事务消息或本地消息表;非核心链路、容忍延迟不一致,用最大努力通知就够了。

判断是否存在分布式事务是前提,选错了方案是浪费,压根没意识到问题才是最危险的。

把三条边界记住,遇到跨服务、跨资源、跨物理节点的场景,停下来想一想,基本上不会跑偏。