分布式事务怎么实现?2PC、TCC、Saga 有什么区别?
🧑💻 面试官:跨服务事务怎么处理?
🙋♂️ 我:用 Saga,失败时回滚。
🧑💻 面试官:第一步已经发送邮件,第二步失败,怎么把邮件回滚到没发过?
🙋♂️ 我:只能补偿,不能撤销已经发生的事实。
🧑💻 面试官:那 2PC、TCC、Saga 和 Outbox 分别解决什么,能随意互换吗?
先确定需要共同提交,还是允许已提交步骤再补偿;补偿不是把时间倒回去。
面试速答(60 秒版)
跨服务各自使用本地事务,无法直接靠一个普通数据库事务覆盖全部资源。2PC 让支持协议的参与者准备后统一决定提交或回滚,但有协调、等待和恢复成本。
TCC 由业务实现 Try 预留、Confirm 确认、Cancel 取消,设计幂等和资源状态的负担更明显。Saga 把流程拆成已提交的本地事务,失败时执行业务补偿,没有通用的全局隔离与物理回滚保证。
Outbox 用同一份本地事务保存业务变化和待发事件,解决相关双写窗口;投递可能重复,消费者仍需幂等。
选择看不变量、资源能否预留、是否允许中间状态和补偿,不能只因为微服务就默认选 Saga。

知识点详解:共同决定和事后补偿是不同承诺
先承认两个数据库之间有故障窗口
假设服务 A 保存任务后,要通知服务 B 分配资源。先写 A 再发消息,进程可能在中间退出;先发消息再写 A,也可能消息已经生效但本地写入失败。
这不是给接口包一层 try/catch 就能消失的问题。网络超时还会让你不知道对方到底执行了没有。
因此要先定义成功状态和不变量,再选择协调或异步恢复机制。
2PC 与 TCC 都有准备阶段,但资源层次不同
2PC 的参与者进入准备状态,再由协调者决定结果。以支持准备事务的数据库为例,等待期间可能继续持有锁,协调者状态与恢复不能丢。
TCC 把预留和确认表达成业务操作,例如先预留某份配额,成功后确认使用,失败后释放。它不要求所有资源都只靠相同数据库指令操作。
Try、Confirm、Cancel 需要应对重试、乱序和失败状态。迟到的 Try 不能把已取消事务重新占住,重复 Confirm 不能重复扣资源。
Saga 的补偿,是新的业务动作
假设先保存任务、再开通资源,后一步失败,可以将任务标为失败并释放已开通资源。先前提交过的记录不必假装没存在过。
如果中间已经对外发送通知,补偿可能是再发更正,而不是把原通知撤销。补偿自身也可能失败,需要重试、报警或人工介入。
Saga 的编排器与事件协同各有复杂度,但都要定义状态和补偿。没有明确补偿能力的动作不能随意当作可逆步骤。
Outbox 解决事件发布,不直接变成全局事务
把本地业务更新和事件记录放在同一事务里,后台读取已提交事件并发送。这样能恢复“业务已完成,事件尚未发出”的窗口。
发送完成后记录进度之前退出,会再次发送。因此消息 ID、消费者幂等、顺序和重复处理仍要设计。
Outbox 可以服务于 Saga 等架构,但它自己不保证所有服务一起提交。验收要注入超时、重复投递、协调者退出和补偿失败,而不只跑正常路径。
本题机制参考:PostgreSQL 准备事务、Apache Seata TCC、AWS Saga 编排、Transactional Outbox 原作者说明。

面试官继续追问
Saga 能保证全局数据库隔离吗?
通常不能按普通单库事务理解。中间步骤已提交,其他操作可能观察到,需要业务隔离策略。
2PC 没有任何失败风险吗?
不是。需要持久化决策、参与者恢复和协调故障处理,长时间准备还会占资源。
Outbox 投递算恰好一次吗?
不能默认。发送与记进度之间仍有重复窗口,效果去重要由消费者等机制保障。
面试速记卡
- 2PC:支持协议的资源准备,再统一决定。
- TCC:业务预留、确认、取消,状态与幂等自己设计。
- Saga:本地提交+业务补偿,不是倒带。
- Outbox:业务和事件同库事务,投递仍可能重复。
- 选型:不变量、资源预留和中间状态容忍度。
公司面试真题
真题根据求职者公开面经整理,题意经过概括,非逐字原话或公司官方题库;本文为 Sunday 的独立解析。
字节跳动 · 后端(TikTok) · 日常实习
出现跨库事务时怎样处理?(题意整理)
tiktok后端开发日常实习面经后续(已OC) ↗
面试记录为 2024-11-14;原帖编辑于 2024-11-16美团 · Java后端 · 实习
分布式事务有哪些实现方案?(题意整理)
4.21美团Java实习一二面面经 ↗
面试记录为 2020-04-21、2020-04-24;原帖编辑于 2020-11-14