分布式事务实现

"Distributed_transaction_implementation"

Posted by LanZinYtt on May 2, 2026

分布式系统中,一个业务往往会跨越多个服务、多个数据库,单机事务已经无法直接保证整体一致性,因此需要分布式事务方案。

本文不仅介绍 Seata XASeata ATSeata TCCSeata SagaMQ 事务消息本地消息表,也重点梳理:

  • 每种方案落地时要实现什么
  • 各个服务是否需要额外接口
  • 是否需要增加数据表
  • 一个统一的样例如何套到各个方案里

部分内容参考b站视频【18分钟彻底掌握分布式事务(7种解决方案+Seata实战演示)】

alt text

1. 分布式事务要解决什么问题

核心目标通常有两个:

  • 数据一致性:多个服务要么一起成功(强一致性),要么最终达到一致
  • 系统可用性:不能因为强一致而让系统吞吐和可用性大幅下降

因此,不同方案本质上是在 一致性、性能、侵入性、实现复杂度 之间做权衡。


2. Seata 实现方案

Seata 是 Java 生态中较常见的分布式事务框架,不同模式适用于不同业务场景。

2.1 XA 模式

XA 属于经典的两阶段提交协议(2PC):

  1. 一阶段:各参与者执行本地事务,但不提交,先锁定资源
  2. 二阶段:事务协调器根据各分支执行结果,统一决定提交或回滚

优点:

  • 一致性强
  • 对业务代码侵入较小

缺点:

  • 资源锁持有时间长
  • 性能较差,并发能力弱
  • 对数据库 XA 能力有要求

落地时需要做什么:

  • 各参与服务接入 Seata Server
  • 数据源切换为支持 XA 的代理数据源
  • 业务入口加全局事务注解,如 @GlobalTransactional
  • 分支服务正常写本地事务逻辑

是否需要额外接口:

  • 一般不需要额外的业务补偿接口
  • 只需要正常的服务调用接口

是否需要增加数据表:

  • 业务表通常不需要专门为 XA 改结构
  • 主要依赖 Seata 服务端和数据库 XA 能力

样例:

例如“创建订单 + 扣库存”场景中:

  • order-service 开启全局事务
  • 调用 stock-service 扣库存
  • 两边本地事务进入 prepare 状态
  • 任一步失败,则协调器统一通知回滚

适用场景:

  • 对强一致性要求高
  • 并发不是特别大的核心业务

2.2 AT 模式

AT 是 Seata 默认推荐的模式,属于 最终一致性 + 自动补偿

其核心流程:

  1. 执行业务 SQL 前后记录镜像数据
  2. 分支事务直接提交本地事务
  3. 全局事务成功则结束
  4. 全局事务失败则根据镜像自动回滚

优点:

  • 对业务侵入低
  • 开发成本较低
  • 相比 XA 性能更好

缺点:

  • 依赖关系型数据库和代理数据源
  • 回滚基于数据镜像,不适合所有复杂 SQL
  • 本质不是严格强一致

落地时需要做什么:

  • 各服务接入 Seata 客户端
  • 使用 DataSourceProxy 代理业务数据源
  • 事务入口加 @GlobalTransactional
  • 分支服务正常使用本地事务

是否需要额外接口:

  • 一般不需要额外补偿接口
  • 回滚由 Seata 基于 undo_log 自动完成

是否需要增加数据表:

  • 需要在每个参与事务的数据库中增加 undo_log
  • 业务表一般不需要额外字段

样例:

下单流程:

  1. order-service 创建订单记录
  2. stock-service 扣减库存
  3. account-service 扣减余额
  4. 若扣余额失败,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 适合 长事务。核心思想是:

  • 将大事务拆成多个本地事务
  • 每一步成功后继续下一步
  • 如果中途失败,则按相反顺序执行补偿

例如:下单 → 扣库存 → 扣余额 → 生成物流单;若失败,则按逆序补偿。

优点:

  • 适合流程长、步骤多的业务
  • 不需要长时间持有数据库锁
  • 可用性高

缺点:

  • 只能保证最终一致
  • 补偿逻辑复杂
  • 业务中间态处理较麻烦

落地时需要做什么:

  • 定义完整的状态机或流程编排
  • 每个本地事务都准备对应补偿动作
  • 由状态机引擎或流程编排器驱动执行

是否需要额外接口:

  • 需要补偿接口,或者至少需要正向动作与逆向动作
  • 例如:创建订单 / 取消订单,扣库存 / 恢复库存

是否需要增加数据表:

  • 通常建议增加:
    • 流程状态表
    • 步骤执行表
    • 补偿记录表

样例:

下单长流程:

  1. 创建订单
  2. 扣库存
  3. 扣余额
  4. 创建物流单

若第 4 步失败,则可能补偿:

  • 退余额
  • 恢复库存
  • 取消订单

适用场景:

  • 跨服务编排流程
  • 业务链路长、无法接受长时间锁资源

3. MQ 事务消息

事务消息的核心思想是:把跨服务一致性问题转成“本地事务 + 消息投递一致性”问题。

典型流程如下:

  1. 生产者先发送半消息(Prepare Message)
  2. 执行本地事务
  3. 本地事务成功,则提交消息;失败则回滚消息
  4. 消费者收到消息后执行自己的业务逻辑

如果 MQ 长时间没收到提交/回滚结果,还会回查生产者事务状态。

优点:

  • 解耦明显
  • 吞吐高,适合异步场景
  • 非常适合最终一致性业务

缺点:

  • 业务不是实时强一致
  • 需要处理消息重复、消费幂等、消息积压等问题

落地时需要做什么:

  • 生产者支持事务消息发送
  • 本地事务结果可反馈给 MQ
  • 消费者实现幂等消费

是否需要额外接口:

  • 一般不需要补偿接口
  • 但生产者通常需要实现事务回查逻辑

是否需要增加数据表:

  • 不一定必须增加
  • 但生产环境通常会加:
    • 消费幂等表
    • 消费记录表
    • 业务去重表

样例:

例如订单服务发送“订单已创建”事务消息:

  1. 先发半消息给 MQ
  2. 本地事务插入订单表
  3. 成功后提交消息
  4. 积分服务收到消息后给用户加积分

若 MQ 未收到明确结果,则回查订单服务本地事务状态。

适用场景:

  • 订单创建后通知积分、物流、营销系统
  • 异步驱动的业务链路

4. 本地消息表

本地消息表是非常经典的最终一致性方案。

核心做法:

  1. 业务操作和消息记录写入 同一个本地数据库事务
  2. 本地事务提交成功后,后台任务扫描消息表
  3. 将未投递消息发送到 MQ 或下游服务
  4. 消费成功后更新消息状态

这样可以保证:只要本地事务成功,消息最终一定能被补发出去。

优点:

  • 实现简单,容易理解
  • 不强依赖 MQ 的事务能力
  • 可靠性较高

缺点:

  • 需要额外维护消息表
  • 存在消息投递延迟
  • 也要处理幂等、重试、死信等问题

落地时需要做什么:

  • 在业务库中增加消息表
  • 业务数据和消息记录在同一本地事务中提交
  • 增加扫描、重试、投递任务
  • 消费方实现幂等控制

是否需要额外接口:

  • 一般不需要专门的分布式事务接口
  • 发送方需要消息投递逻辑
  • 接收方需要正常业务处理接口

是否需要增加数据表:

  • 需要增加本地消息表
  • 常见字段包括:
    • message_id
    • biz_type
    • payload
    • status
    • retry_count
    • next_retry_time

样例:

例如创建订单时:

  1. 插入订单表
  2. 同时插入一条“订单已创建”的消息到本地消息表
  3. 后台任务扫描未发送消息并投递到 MQ
  4. 积分服务消费成功后,消息状态更新为已完成

适用场景:

  • 中小型系统
  • 希望用较低成本实现最终一致性

5. 从“实现成本”角度再看一遍

如果只关心“服务要多写什么、库里要多加什么”,可以快速总结为:

方案 服务要额外实现什么 是否要额外接口 是否要额外数据表
XA 接入 XA 数据源与全局事务 一般不需要 通常不需要业务表改造
AT 接入 Seata 代理数据源 一般不需要 需要 undo_log
TCC 实现 Try/Confirm/Cancel 需要 通常需要冻结字段/幂等表
Saga 实现正向动作和补偿动作 需要 建议增加流程状态表
MQ 事务消息 实现事务消息发送与回查 可能需要回查逻辑 建议加幂等/消费记录表
本地消息表 实现消息落表、扫描、重试 一般不需要 需要本地消息表

6. 一个很小的统一业务样例

假设有一个最简单的业务:用户下单后,需要扣库存、扣余额,并最终创建订单。

6.1 XA / AT 的写法

  • order-service 作为事务入口
  • 远程调用 stock-serviceaccount-service
  • 失败时由框架统一回滚
  • 开发者主要负责正常业务代码

6.2 TCC 的写法

  • stock-service:实现库存冻结、确认扣减、取消释放
  • account-service:实现余额冻结、确认扣减、取消恢复
  • order-service:协调整条事务

6.3 Saga 的写法

  • 正向:创建订单 → 扣库存 → 扣余额
  • 补偿:取消订单 ← 恢复库存 ← 恢复余额

6.4 MQ 事务消息的写法

  • order-service 本地提交订单后发送事务消息
  • stock-serviceaccount-service 订阅消息后各自处理
  • 通过幂等保证重复消息不出错

6.5 本地消息表的写法

  • order-service 本地事务中写入订单 + 消息表
  • 定时投递消息给库存、账户服务
  • 下游消费完成后更新状态

7. 各方案对比

方案 一致性 性能 业务侵入 实现复杂度 典型场景
XA 强一致 较低 核心强一致业务
AT 最终一致 较高 常规数据库事务场景
TCC 强一致/准强一致 较高 资金、库存
Saga 最终一致 长流程业务
MQ 事务消息 最终一致 异步解耦场景
本地消息表 最终一致 较高 通用可靠消息场景

8. 选型建议

可以按下面思路选择:

  • 要求强一致,且并发不高:优先考虑 XA
  • 主要是数据库 CRUD,想快速接入:优先考虑 AT
  • 核心业务、高并发、可接受较高开发成本:优先考虑 TCC
  • 流程长、跨多个服务节点:优先考虑 Saga
  • 更关注异步解耦和吞吐:优先考虑 MQ 事务消息
  • 想要低成本实现可靠最终一致:优先考虑 本地消息表

9. 总结

分布式事务没有绝对最优解,只有更适合当前业务的方案。

  • 追求强一致:看 XA / TCC
  • 追求低侵入:看 AT
  • 追求长流程编排:看 Saga
  • 追求异步解耦:看 MQ 事务消息 / 本地消息表

实际工程中,往往不是只用一种,而是根据业务重要性分层选型:核心链路偏强一致,边缘链路偏最终一致,这通常才是更合理的设计。