5种方案 + Seata + 选型决策 · 业务场景对照 · 完整代码注释 · 陷阱细节
单库场景下,`@Transactional` 一把锁搞定 — 数据库内部的 redo/undo log 保证 ACID。但分布式场景下,一个业务跨多个独立数据库,每个库有自己的 @Transactional,谁也管不了别人。
用户下单要操作 4 个独立服务:订单服务(订单库A)+ 库存服务(库存库B)+ 积分服务(积分库C)+ 支付服务(支付库D)。
如果积分加失败,但订单已成功、库存已扣 → 数据不一致!
每个服务有自己的 @Transactional,但跨服务就管不到了 — 这就需要"协调者"统一管这 4 个本地事务,要么全成,要么全败。这就是分布式事务。
选型前必须懂这两个理论,它们决定了分布式事务方案的"天花板"。
| 理论 | 核心 | 含义 |
|---|---|---|
| CAP | 三选二 | C 一致性(所有节点数据一致)· A 可用性(每次请求有响应)· P 分区容错(网络分区时还能跑)。分布式系统 P 必选,所以只能在 CA 之间二选一。 |
| BASE | 最终一致 | BAsically Available(基本可用)· Soft state(软状态,中间态可见)· Eventually consistent(最终一致)。是 AP 的实践指南 — 放弃强一致,换可用性。 |
原理:引入一个"协调者"(Coordinator),分两阶段:
① Prepare 阶段:协调者问所有参与者"能不能提交?",参与者执行本地事务但不 commit,把结果(可以/不行)回给协调者。
② Commit/Rollback 阶段:所有参与者都说"可以" → 协调者发 commit;有任何一个说"不行"或超时 → 协调者发 rollback。
原理:把每个操作拆成三个方法:
① Try:预留资源(不真扣,先冻结)— 比如冻结库存、冻结余额
② Confirm:确认执行(真扣,把冻结的减掉)
③ Cancel:取消执行(解冻,把冻结的还回去)
所有 Try 成功 → 调 Confirm;任何 Try 失败 → 调 Cancel。
原理:把长事务拆成 N 个本地事务 T1→T2→T3→...→Tn,每个 Ti 配一个补偿 Ci。
正向:T1 成功 → T2 成功 → T3 成功 → ...
补偿:T3 失败 → 反向执行 C2 → C1(补偿已执行的)。
不预留资源,执行完就提交,失败靠补偿回滚。
原理:① 业务操作 + 写消息表放同一个本地事务(用 @Transactional 保证原子性);② 后台定时任务扫描消息表,把消息发到 MQ;③ 消费者消费 MQ 执行下游业务,消费成功后回 ACK。
核心:把"业务+消息"绑成一个本地事务,解决了"业务成功但消息丢了"的问题。
原理:RocketMQ 独有,把"本地消息表"的思路内置到 MQ。
① 生产者发半消息(消费者看不到)→ MQ 收到后回调生产者执行本地事务;
② 本地事务成功 → 提交消息(消费者可见);失败 → 回滚消息(删除);
③ 如果生产者超时/挂了 → MQ 主动回查生产者事务状态,根据结果补提交/回滚。
阿里开源,把上述几种模式封装成开箱即用的框架,实际工程用得最多。
| Seata 模式 | 对应方案 | 特点 | 适用场景 |
|---|---|---|---|
| AT 模式(默认) | 2PC 变种 | 业务无侵入(还写 @Transactional),框架自动解析 SQL 生成回滚 SQL | 大部分场景(默认选这个) |
| TCC 模式 | TCC | 业务自己写 Try/Confirm/Cancel | 钱场景,高性能要求 |
| Saga 模式 | Saga | 状态机驱动,长流程补偿 | 跨多服务长流程 |
| XA 模式 | 2PC | 数据库原生 XA 协议 | 跨多个不同数据库,强一致 |
结合资料库里 9 大业务系统,逐一对照该用哪种方案。
下单 + 加积分 + 发短信,最终一致够用。用 RocketMQ 的话直接事务消息;不用 RocketMQ 用本地消息表。
事务消息 本地消息表扣款/转账/退款,钱相关必须强一致。TCC 性能好但要写 3 套,支付核心场景值得。
TCC扣库存 + 生成订单,高并发。最终一致够用,用本地消息表异步落单。强一致扛不住并发。
本地消息表消息必达,最终一致够用。本地消息表 + 重试,不丢消息即可。
本地消息表发帖 + 推 feed + 索引,最终一致。事务消息或本地消息表。
事务消息下单 → 取货 → 中转 → 配送,长流程多环节。Saga 状态机驱动,某步失败补偿前面。
Saga数据同步到数仓,最终一致。本地消息表 + 定时同步。
本地消息表实时决策,要求低延迟。TCC 不合适(写 3 套太重),通常用本地消息表 + 实时特征。
本地消息表租户开通 + 资源初始化,长流程。Saga 编排,某步失败补偿。
Saga最重要的工程建议:别上来就分布式事务。先问三个问题:能不能同库?能不能最终一致?是不是钱相关?
| 方案 | 一致性 | 性能 | 业务侵入 | 推荐度 |
|---|---|---|---|---|
| 2PC | 强一致 | 差(阻塞) | 无 | ⭐ 不推荐 |
| TCC | 强一致 | 好 | 极大(3套) | ⭐⭐⭐⭐ 钱场景 |
| Saga | 最终一致 | 好 | 中(补偿) | ⭐⭐⭐ 长流程 |
| 本地消息表 | 最终一致 | 好 | 小 | ⭐⭐⭐⭐⭐ 工程首选 |
| 事务消息 | 最终一致 | 好 | 小 | ⭐⭐⭐⭐⭐ 电商首选 |
| 业务特征 | 选哪个 |
|---|---|
| 钱相关(支付/转账/退款)+ 高性能 | TCC(Seata-TCC) |
| 长流程跨多服务(旅行预订/理赔) | Saga(Seata-Saga) |
| 普通业务(下单+积分+通知)+ 用 RocketMQ | 事务消息 |
| 普通业务 + 不想引框架 | 本地消息表 |
| 大部分场景,默认选择 | Seata-AT(业务无侵入) |
| 能同库就同库 | @Transactional(根本不是分布式问题) |