面试题:分布式事务怎么做?2PC、TCC、Saga 各解决什么问题、又各有什么坑?
这是「每日一题」专栏的第八篇。每天一道面试题,后端 + AI 混合路线,从原理到代码一次讲清。上一篇讲了 KV Cache 与长上下文推理,今天回到后端/分布式方向,聊聊分布式系统里最经典、也最容易被问住的话题:分布式事务。
题目
后端面试出镜率极高的一道题:
微服务拆分、分库分表之后,本地事务为什么不够用了?分布式事务有哪些主流方案?2PC、TCC、Saga 各自的原理、适用场景和坑是什么?
这道题考察三件事:你知不知道分布式事务到底要解决什么问题、能不能说清三种方案的取舍、有没有踩过「看起来能用、线上就炸」的坑。很多人能背出 TCC 是 Try-Confirm-Cancel,但一问「空回滚」就卡壳。
先给结论,再拆开讲。
一、为什么需要分布式事务
单库单表时代,一个 UPDATE 带两个 INSERT 包在一个本地事务里,要么全成功、要么全回滚,靠数据库的 ACID 就够了。
微服务拆分、分库分表之后,一个业务操作要跨多个服务、多套数据库:
-- 库存服务:扣库存
UPDATE stock SET count = count - 1 WHERE sku_id = 'A001';
-- 订单服务:写订单
INSERT INTO orders (id, user_id, sku_id) VALUES (1, 1001, 'A001');
两个库各自有事务,但没有任何「全局事务」能保证两边一起提交或一起回滚。扣了库存、订单没写成功,或者订单写了、库存没扣,数据就不一致了。
分布式事务的本质:在无法共享锁和日志的多个独立数据库之间,模拟出「要么全成功、要么全回滚」的全局语义。
二、2PC:两阶段提交
2PC(Two-Phase Commit)引入一个协调者(Coordinator),把提交拆成两个阶段:
- 准备阶段(Prepare):协调者问所有参与者「能不能提交?」参与者执行事务但不提交,把结果(yes/no)报回来,期间持有资源锁。
- 提交阶段(Commit/Abort):全部 yes,协调者广播 commit;任何一个 no 或超时,广播 rollback。
协调者 ──Prepare──▶ 参与者A(执行事务,持锁,返回 yes)
协调者 ──Prepare──▶ 参与者B(执行事务,持锁,返回 yes)
协调者 ──Commit──▶ 参与者A(提交,释放锁)
协调者 ──Commit──▶ 参与者B(提交,释放锁)
优点:强一致性,要么全提交、要么全回滚,实现简单(XA 协议就是标准化的 2PC)。
坑(面试重点):
- 同步阻塞:准备阶段所有参与者都持有资源锁,只要有一个参与者慢,全局都卡住,吞吐上不去。
- 协调者单点:协调者挂了,参与者不知道该提交还是回滚,只能干等。
- 脑裂:提交阶段发生网络分区,部分参与者收到 commit、部分没收到,数据照样不一致。2PC 只在「无故障」时保证强一致。
适用场景:银行转账这类跨库强一致要求极高、并发低的场景。互联网业务基本不用裸 2PC——太慢。
三、TCC:业务补偿
TCC(Try-Confirm-Cancel)把每个参与方的操作拆成三个业务方法:
- Try:资源预留(冻结库存、冻结余额),不真正扣减
- Confirm:确认执行(把冻结的库存真正扣掉)
- Cancel:取消执行(解冻之前预留的资源)
订单服务:Try(冻结 1 件库存)──▶ 全部成功 ──▶ Confirm(真正扣减)
账户服务:Try(冻结 100 元) ──▶ 全部成功 ──▶ Confirm(真正扣款)
任一 Try 失败 ──▶ 对已成功的服务逐个调 Cancel(解冻,业务回滚)
优点:不锁数据库资源,Try 阶段只是业务层面的「预留」,并发能力比 2PC 强得多。
坑(面试加分点):
- 空回滚:Try 因网络超时根本没到达服务,但协调者判失败直接调 Cancel,Cancel 拿到一个「从没 Try 过」的事务。必须记录事务状态,Cancel 前先查 Try 是否执行过,没执行就跳过。
- 悬挂:Cancel 先到了并执行完,迟到的 Try 才到达——它把资源又预留了一次,之后没人再 Cancel,资源永久挂住。所以 Try 里也要检查是否已被 Cancel。
- 幂等:Confirm/Cancel 都可能因网络重试被调多次,必须做成幂等的。
适用场景:对一致性要求高、又需要一定并发能力的业务——支付、下单、库存。
四、Saga:长事务补偿
Saga 不锁资源、不预留,直接执行正向操作,出错了用逆向补偿操作把已执行的部分撤销。每一步本地事务都正常提交。
正向:扣库存 ──▶ 减余额 ──▶ 创建订单
出错:第 2 步失败
补偿:回滚第 1 步(把库存加回来)
两种编排方式:
- 编排式(Orchestration):一个 Saga 协调器(如 Seata、Temporal)像写代码一样定义流程,步骤失败走补偿分支。流程清晰、好调试,是目前主流。
- 协同式(Choreography):每个服务完成后发事件,下一个服务监听事件继续。没有中心节点,但流程散落在事件里,出问题难排查。
编排式:
Saga 协调器 ──▶ 扣库存 ──▶ 减余额 ──▶ 创建订单
失败 ◀── 协调器判失败
Saga 协调器 ──▶ 补偿:加回库存(撤销第 1 步)
优点:不持锁、性能好,天然适合跨多天的长事务流程。
坑:
- 补偿逻辑要自己写,等于每个服务多维护一套「反操作」。
- 补偿也可能失败,需要重试加人工兜底。
- 只能保证最终一致,中间状态对外可见。
适用场景:电商下单、订单履约这类长链路、能接受弱一致的场景。
五、怎么选
| 方案 | 一致性 | 锁/预留 | 复杂度 | 典型场景 |
|---|---|---|---|---|
| 2PC/XA | 强一致 | 锁数据库资源 | 低 | 跨库强一致、低并发 |
| TCC | 较强(业务层控制) | Try 预留 | 高(三方法 + 空回滚/悬挂/幂等) | 支付、库存、高并发 |
| Saga | 最终一致 | 不锁 | 中(补偿逻辑) | 长链路业务流 |
一句话选型:要强一致且不在乎并发,选 2PC;要一致性又要并发,选 TCC;链路长、能接受最终一致,选 Saga。 另外还有本地消息表、MQ 事务消息这类「最终一致」的轻量方案,面试中可以补充提一句,显得知识面完整。
面试回答思路
- 先讲为什么:分库分表/微服务后,本地事务管不到别的库,需要跨库的「全局事务」语义。
- 给三个方案:2PC 两阶段强一致但阻塞;TCC 业务补偿三方法,能扛并发但要处理空回滚和悬挂;Saga 正向执行加逆向补偿,最终一致适合长事务。
- 主动讲坑:2PC 的协调者单点和脑裂、TCC 的空回滚/悬挂/幂等、Saga 的补偿失败兜底——能把坑讲明白的候选人,明显比只会背名字的强。
- 拔高一句:「没有银弹。分布式事务的本质,要么用可用性换一致性(2PC),要么用业务补偿换全局锁(TCC/Saga)。」能说到这层,面试官会高看你一眼。
总结
| 问题 | 答案 |
|---|---|
| 为什么需要分布式事务? | 跨库/跨服务无法共享本地事务,需要全局一致性语义 |
| 2PC 怎么工作? | 协调者 Prepare 全员,全部 yes 才 Commit,否则回滚 |
| 2PC 的缺点? | 同步阻塞、协调者单点、提交阶段脑裂 |
| TCC 怎么工作? | Try 预留,Confirm 确认,Cancel 补偿 |
| TCC 的三个坑? | 空回滚、悬挂、幂等 |
| Saga 怎么工作? | 正向执行加逆向补偿,支持长事务 |
| 怎么选? | 强一致低并发选 2PC,高并发强一致选 TCC,长链路弱一致选 Saga |
分布式事务这道题,本质考的是「一致性模型的理解 + 业务场景的取舍」。先把三种方案的机制讲清楚,再主动抛出坑和选型,就能从「背答案」变成「有工程判断」。
明日预告:系统设计方向 —「设计一个秒杀系统:怎么扛住瞬时洪峰、怎么防超卖、消息队列怎么削峰?」