分布式系统中,一个业务往往会跨越多个服务、多个数据库,单机事务已经无法直接保证整体一致性,因此需要分布式事务方案。
本文不仅介绍 Seata XA、Seata AT、Seata TCC、Seata Saga、MQ 事务消息、本地消息表,也重点梳理:
- 每种方案落地时要实现什么
- 各个服务是否需要额外接口
- 是否需要增加数据表
- 一个统一的样例如何套到各个方案里
部分内容参考b站视频【18分钟彻底掌握分布式事务(7种解决方案+Seata实战演示)】

1. 分布式事务要解决什么问题
核心目标通常有两个:
- 数据一致性:多个服务要么一起成功(强一致性),要么最终达到一致
- 系统可用性:不能因为强一致而让系统吞吐和可用性大幅下降
因此,不同方案本质上是在 一致性、性能、侵入性、实现复杂度 之间做权衡。
2. Seata 实现方案
Seata 是 Java 生态中较常见的分布式事务框架,不同模式适用于不同业务场景。
2.1 XA 模式
XA 属于经典的两阶段提交协议(2PC):
- 一阶段:各参与者执行本地事务,但不提交,先锁定资源
- 二阶段:事务协调器根据各分支执行结果,统一决定提交或回滚
优点:
- 一致性强
- 对业务代码侵入较小
缺点:
- 资源锁持有时间长
- 性能较差,并发能力弱
- 对数据库 XA 能力有要求
落地时需要做什么:
- 各参与服务接入
Seata Server - 数据源切换为支持
XA的代理数据源 - 业务入口加全局事务注解,如
@GlobalTransactional - 分支服务正常写本地事务逻辑
是否需要额外接口:
- 一般不需要额外的业务补偿接口
- 只需要正常的服务调用接口
是否需要增加数据表:
- 业务表通常不需要专门为 XA 改结构
- 主要依赖 Seata 服务端和数据库 XA 能力
样例:
例如“创建订单 + 扣库存”场景中:
order-service开启全局事务- 调用
stock-service扣库存 - 两边本地事务进入 prepare 状态
- 任一步失败,则协调器统一通知回滚
适用场景:
- 对强一致性要求高
- 并发不是特别大的核心业务
2.2 AT 模式
AT 是 Seata 默认推荐的模式,属于 最终一致性 + 自动补偿。
其核心流程:
- 执行业务 SQL 前后记录镜像数据
- 分支事务直接提交本地事务
- 全局事务成功则结束
- 全局事务失败则根据镜像自动回滚
优点:
- 对业务侵入低
- 开发成本较低
- 相比 XA 性能更好
缺点:
- 依赖关系型数据库和代理数据源
- 回滚基于数据镜像,不适合所有复杂 SQL
- 本质不是严格强一致
落地时需要做什么:
- 各服务接入 Seata 客户端
- 使用
DataSourceProxy代理业务数据源 - 事务入口加
@GlobalTransactional - 分支服务正常使用本地事务
是否需要额外接口:
- 一般不需要额外补偿接口
- 回滚由 Seata 基于
undo_log自动完成
是否需要增加数据表:
- 需要在每个参与事务的数据库中增加
undo_log表 - 业务表一般不需要额外字段
样例:
下单流程:
order-service创建订单记录stock-service扣减库存account-service扣减余额- 若扣余额失败,Seata 根据各库的
undo_log回滚订单与库存
适用场景:
- 电商、后台管理等常规 CRUD 业务
- 希望快速接入分布式事务能力
2.3 TCC 模式
TCC 即 Try、Confirm、Cancel。
- Try:预留资源、完成校验
- Confirm:正式提交
- Cancel:释放预留资源、执行补偿
它把事务控制下沉到业务层,本质是业务自己实现二阶段提交。
优点:
- 一致性较强
- 性能通常优于 XA
- 适合高并发场景
缺点:
- 代码侵入性强
- 需要手写 Confirm / Cancel
- 要处理幂等、空回滚、悬挂等问题
落地时需要做什么:
- 每个参与服务显式实现
Try / Confirm / Cancel - Try 阶段做资源冻结
- Confirm 阶段做正式扣减
- Cancel 阶段做资源恢复
是否需要额外接口:
- 需要
- 通常每个参与者都需要三类能力:
- Try 接口
- Confirm 接口
- Cancel 接口
是否需要增加数据表:
- 通常需要增加业务辅助字段或辅助表
- 例如:
- 账户表增加
freeze_amount - 库存表增加
freeze_stock - 增加幂等记录表或 TCC 执行记录表
- 账户表增加
样例:
以账户扣款为例:
- Try:可用余额减 100,冻结金额加 100
- Confirm:冻结金额正式扣除
- Cancel:冻结金额恢复到可用余额
适用场景:
- 资金、库存、优惠券等关键业务
- 业务动作明确,便于拆分 Try/Confirm/Cancel
2.4 Saga 模式
Saga 适合 长事务。核心思想是:
- 将大事务拆成多个本地事务
- 每一步成功后继续下一步
- 如果中途失败,则按相反顺序执行补偿
例如:下单 → 扣库存 → 扣余额 → 生成物流单;若失败,则按逆序补偿。
优点:
- 适合流程长、步骤多的业务
- 不需要长时间持有数据库锁
- 可用性高
缺点:
- 只能保证最终一致
- 补偿逻辑复杂
- 业务中间态处理较麻烦
落地时需要做什么:
- 定义完整的状态机或流程编排
- 每个本地事务都准备对应补偿动作
- 由状态机引擎或流程编排器驱动执行
是否需要额外接口:
- 需要补偿接口,或者至少需要正向动作与逆向动作
- 例如:创建订单 / 取消订单,扣库存 / 恢复库存
是否需要增加数据表:
- 通常建议增加:
- 流程状态表
- 步骤执行表
- 补偿记录表
样例:
下单长流程:
- 创建订单
- 扣库存
- 扣余额
- 创建物流单
若第 4 步失败,则可能补偿:
- 退余额
- 恢复库存
- 取消订单
适用场景:
- 跨服务编排流程
- 业务链路长、无法接受长时间锁资源
3. MQ 事务消息
事务消息的核心思想是:把跨服务一致性问题转成“本地事务 + 消息投递一致性”问题。
典型流程如下:
- 生产者先发送半消息(Prepare Message)
- 执行本地事务
- 本地事务成功,则提交消息;失败则回滚消息
- 消费者收到消息后执行自己的业务逻辑
如果 MQ 长时间没收到提交/回滚结果,还会回查生产者事务状态。
优点:
- 解耦明显
- 吞吐高,适合异步场景
- 非常适合最终一致性业务
缺点:
- 业务不是实时强一致
- 需要处理消息重复、消费幂等、消息积压等问题
落地时需要做什么:
- 生产者支持事务消息发送
- 本地事务结果可反馈给 MQ
- 消费者实现幂等消费
是否需要额外接口:
- 一般不需要补偿接口
- 但生产者通常需要实现事务回查逻辑
是否需要增加数据表:
- 不一定必须增加
- 但生产环境通常会加:
- 消费幂等表
- 消费记录表
- 业务去重表
样例:
例如订单服务发送“订单已创建”事务消息:
- 先发半消息给 MQ
- 本地事务插入订单表
- 成功后提交消息
- 积分服务收到消息后给用户加积分
若 MQ 未收到明确结果,则回查订单服务本地事务状态。
适用场景:
- 订单创建后通知积分、物流、营销系统
- 异步驱动的业务链路
4. 本地消息表
本地消息表是非常经典的最终一致性方案。
核心做法:
- 业务操作和消息记录写入 同一个本地数据库事务
- 本地事务提交成功后,后台任务扫描消息表
- 将未投递消息发送到 MQ 或下游服务
- 消费成功后更新消息状态
这样可以保证:只要本地事务成功,消息最终一定能被补发出去。
优点:
- 实现简单,容易理解
- 不强依赖 MQ 的事务能力
- 可靠性较高
缺点:
- 需要额外维护消息表
- 存在消息投递延迟
- 也要处理幂等、重试、死信等问题
落地时需要做什么:
- 在业务库中增加消息表
- 业务数据和消息记录在同一本地事务中提交
- 增加扫描、重试、投递任务
- 消费方实现幂等控制
是否需要额外接口:
- 一般不需要专门的分布式事务接口
- 发送方需要消息投递逻辑
- 接收方需要正常业务处理接口
是否需要增加数据表:
- 需要增加本地消息表
- 常见字段包括:
message_idbiz_typepayloadstatusretry_countnext_retry_time
样例:
例如创建订单时:
- 插入订单表
- 同时插入一条“订单已创建”的消息到本地消息表
- 后台任务扫描未发送消息并投递到 MQ
- 积分服务消费成功后,消息状态更新为已完成
适用场景:
- 中小型系统
- 希望用较低成本实现最终一致性
5. 从“实现成本”角度再看一遍
如果只关心“服务要多写什么、库里要多加什么”,可以快速总结为:
| 方案 | 服务要额外实现什么 | 是否要额外接口 | 是否要额外数据表 |
|---|---|---|---|
| XA | 接入 XA 数据源与全局事务 | 一般不需要 | 通常不需要业务表改造 |
| AT | 接入 Seata 代理数据源 | 一般不需要 | 需要 undo_log |
| TCC | 实现 Try/Confirm/Cancel | 需要 | 通常需要冻结字段/幂等表 |
| Saga | 实现正向动作和补偿动作 | 需要 | 建议增加流程状态表 |
| MQ 事务消息 | 实现事务消息发送与回查 | 可能需要回查逻辑 | 建议加幂等/消费记录表 |
| 本地消息表 | 实现消息落表、扫描、重试 | 一般不需要 | 需要本地消息表 |
6. 一个很小的统一业务样例
假设有一个最简单的业务:用户下单后,需要扣库存、扣余额,并最终创建订单。
6.1 XA / AT 的写法
order-service作为事务入口- 远程调用
stock-service、account-service - 失败时由框架统一回滚
- 开发者主要负责正常业务代码
6.2 TCC 的写法
stock-service:实现库存冻结、确认扣减、取消释放account-service:实现余额冻结、确认扣减、取消恢复order-service:协调整条事务
6.3 Saga 的写法
- 正向:创建订单 → 扣库存 → 扣余额
- 补偿:取消订单 ← 恢复库存 ← 恢复余额
6.4 MQ 事务消息的写法
order-service本地提交订单后发送事务消息stock-service、account-service订阅消息后各自处理- 通过幂等保证重复消息不出错
6.5 本地消息表的写法
order-service本地事务中写入订单 + 消息表- 定时投递消息给库存、账户服务
- 下游消费完成后更新状态
7. 各方案对比
| 方案 | 一致性 | 性能 | 业务侵入 | 实现复杂度 | 典型场景 |
|---|---|---|---|---|---|
| XA | 强一致 | 较低 | 低 | 中 | 核心强一致业务 |
| AT | 最终一致 | 较高 | 低 | 低 | 常规数据库事务场景 |
| TCC | 强一致/准强一致 | 较高 | 高 | 高 | 资金、库存 |
| Saga | 最终一致 | 高 | 中 | 高 | 长流程业务 |
| MQ 事务消息 | 最终一致 | 高 | 中 | 中 | 异步解耦场景 |
| 本地消息表 | 最终一致 | 较高 | 中 | 中 | 通用可靠消息场景 |
8. 选型建议
可以按下面思路选择:
- 要求强一致,且并发不高:优先考虑
XA - 主要是数据库 CRUD,想快速接入:优先考虑
AT - 核心业务、高并发、可接受较高开发成本:优先考虑
TCC - 流程长、跨多个服务节点:优先考虑
Saga - 更关注异步解耦和吞吐:优先考虑
MQ 事务消息 - 想要低成本实现可靠最终一致:优先考虑
本地消息表
9. 总结
分布式事务没有绝对最优解,只有更适合当前业务的方案。
- 追求强一致:看
XA / TCC - 追求低侵入:看
AT - 追求长流程编排:看
Saga - 追求异步解耦:看
MQ 事务消息 / 本地消息表
实际工程中,往往不是只用一种,而是根据业务重要性分层选型:核心链路偏强一致,边缘链路偏最终一致,这通常才是更合理的设计。