想象你在筹备一场跨国婚宴:场地在巴厘岛、厨师从上海飞、摄影师来自巴黎、乐队从东京请。任何一方缺席,婚礼都无法完美。更麻烦的是,你不可能等开席之后才发现厨师没到——你需要在开席前,就让所有人拍着胸脯保证“我一定到”。 在软件世界,这个“总策划”叫两阶段提交(2PC)。它把一次跨多个数据库的操作拆成两轮:第一轮挨个问“能办到吗”,大家都说“能”,才进入第二轮——一声令下,同时执行。听上去很简单,但它背后藏着深刻的工程权衡:为了不出现“一半开席一半没来”的乱子,所有参与者都得先放下手头的活、占着资源傻等;万一总策划自己失联,整个婚礼就僵在原地;极端情况下甚至仍可能出乱子。这篇文章,就跟着这场婚宴,把这些门道一层层拆开看。
对后端与数据方向的 AI 从业者而言,2PC 是理解分布式一致性的第一块基石——读懂它的原理与代价,才能在架构选型和故障排查中做出靠谱决策。
可视化演示
动态演示分布式事务2PC的核心机制,通过基础/核心/应用/扩展四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
分布式事务2PC:PC协议 2PC协议的协调者与参与者角色 Prepare准备阶段的执行流 核心机制可视化 分布式事务2PC整体架构与关键组件
分布式事务背景
筹备跨国婚宴时你会发现,本地“家务事”好办——买菜、订桌、找司仪,一个管家就能搞定。可一旦涉及场地、厨师、摄影师、乐队这些“外部供应商”,每一家都有自己的日程和标准:场地可能同时接了别的活,厨师可能临时改航班。你没法用“家里的一套规矩”要求他们,只能靠反复沟通、统一约定。
软件里的“家务事”就是本地事务:在一个数据库内,事务的 ACID(原子性、一致性、隔离性、持久性)由数据库引擎保证,要么全成功、要么全失败。但当业务数据分散在多个数据库——比如订单在订单库、库存在库存库、余额在账户库——一次操作要同时改动三个库,本地事务就“各管各的”了。这就是分布式事务要解决的问题:如何让跨库的多步操作像本地事务一样原子。
分布式系统有个著名的 CAP 定理:一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)三者最多同时满足两个。网络分区(P)在现实中无法避免,所以真正的选择发生在 C 和 A 之间。2PC 是“CP 派”的代表:它宁可让系统在提交期间不可用,也要保证所有节点最终状态一致。
就像为了婚宴不出岔子,总策划宁可让所有供应商在开席前“原地待命”,也不允许出现“这边开席、那边没来”的情况——这就是用一小段等待,换一个确定的结局。
- C(一致性)所有节点在同一时刻读到相同的数据,相当于婚宴上每桌的菜必须同时上齐、内容一致。
- A(可用性)系统始终能及时响应请求,相当于婚宴必须按计划开席,不能无限期推迟。
- P(分区容错性)即使部分节点之间网络断裂,系统也要继续工作,相当于个别供应商失联时婚宴不能直接停摆。
- ≤ 2(三选二)三个目标只能同时满足两个;分布式系统必须接受 P,剩下的取舍就在 C 和 A 之间。
两阶段提交流程
总策划定下规矩:所有供应商必须经历两轮确认。第一轮是“探底”——逐个打电话:“这周六的食材能备齐吗?摄影档期能留吗?乐队能到场吗?”每个供应商收到询问后,开始准备并锁定档期,然后回话:“能”或“不能”。注意,这时他们只是“待命”,还没有真正开工。
这就是两阶段提交(2PC)的“准备阶段”(Prepare Phase):协调者向所有参与者发送 prepare 请求;每个参与者执行本地事务到“可提交”状态——写入 undo/redo 日志、对涉及的数据加锁——然后向协调者回复 YES(同意提交)或 NO(无法提交)。参与者绝不能在这时提前提交,因为最终决定权还在协调者手里。
当协调者收齐全部投票,进入“提交阶段”(Commit Phase):如果所有投票都是 YES,协调者广播 commit 指令,各参与者正式提交并释放锁;如果存在任意一个 NO,协调者广播 abort 指令,各参与者利用 undo 日志回滚到事务开始前。两个阶段的语义可以概括为:先确认“每个人都办得到”,再执行“所有人都同时办”。
回到婚宴:先确认“每家都能来”,再宣布“开席”。这样一来,“10 桌开席、2 桌没上菜”的场面被从流程上杜绝了——要么全场照常,要么全场取消,不存在中间态。
- Vote_i第 i 个参与者的投票,取值只有 YES 或 NO,就像供应商只能回答“能”或“不能”。
- ∧(逻辑与/全称)对所有参与者的投票做“与”运算,任何一个不是 YES,整体就不成立,即要求每个 i 都投 YES。
- ∃(存在)只要存在一个 NO,协调者就触发中止分支,广播 Abort,不提交任何节点。
- 整体含义最终结果只有“全提交”或“全回滚”两种,这就是“全有或全无”的原子性来源。
协议角色职责
婚宴筹备里,总策划和供应商的分工很清晰:策划师不亲自炒菜、不亲自摄影,他只负责三件事——发出询问、收集回复、下达最终指令;而每个供应商只对自己的环节负责,接到指令就执行,没接到指令就待命。
2PC 同样只有两类角色:协调者(Coordinator)与参与者(Participant)。协调者通常位于发起事务的应用节点或专门的事务管理器上,是整个协议的大脑;参与者是各资源管理器(数据库、消息队列等),负责执行本地事务。协调者与参与者之间通过标准化的 prepare / commit / abort 消息交互,协议本身不关心业务逻辑。
参与者的本地状态是一个有限状态机:初始为 WORKING(事务执行中),prepare 成功后进入 PREPARED(已锁定资源、已写日志),最终根据指令到达 COMMITTED 或 ABORTED。协调者也要持久化自己的决策日志(比如“已发送 commit”),否则它宕机恢复后会忘记自己推进到哪一步,导致无法恢复现场。
这就像每家供应商虽然各有各的厨房和流程,但都必须遵守策划师的“状态协议”:备料中、就绪待命、开工、停工。谁也不能绕过这些状态自作主张——这正是 2PC 能把多方行为“对齐”到同一节奏上的根本原因。
- State_i参与者 i 在任意时刻所处的本地事务状态。
- WORKING事务正在本地执行,变更尚未定型,相当于厨师正在备菜。
- PREPARED已写日志、已锁资源,等待协调者的最终指令,相当于菜已备好、灶台被占住。
- COMMITTED / ABORTED收到最终指令后的两种归宿——要么开席,要么取消。
2PC核心优势
策划师这套“两轮确认”虽然繁琐,但换来的是所有人都最看重的东西——确定性:要么全场开席,要么全场取消,绝不存在“主桌开席、乐队没来”的闹剧。对于婚宴这种不容闪失的大事,确定性比效率更值钱。
2PC 的核心价值就是强一致性。在提交阶段,所有参与者的提交指令由同一个协调者发出,配合准备阶段对资源的全局锁定,可以严格保证事务的原子性(Atomicity)——全有或全无,没有任何中间状态暴露给外部。
从工程角度看,2PC 的优点还有:实现相对简单,业务代码不需要写补偿逻辑,只需处理正常提交/回滚两个分支;同时它被 XA 规范采用,成为事实标准,MySQL、Oracle、PostgreSQL 等主流数据库都内置支持,可以用最少的定制代码接入。这也是为什么很多金融级系统至今仍用 2PC 处理核心账务。
一句话:如果你需要的是一份“同一时刻、全体一致”的硬承诺,2PC 是历史上最成熟、最不需要业务方操心的方案——婚宴图省心,就找总策划全权指挥。
- tx_i参与者 i 上的本地事务,相当于每个供应商手头的“那么活儿”。
- 全体 Commit所有参与者的事务全部成功提交,没有一个落下。
- 全体 Abort所有参与者的事务全部回滚,协议禁止“部分提交”的中间状态。
- ∨(二选一)两个结果互斥且必居其一,这就是业务代码只需处理两个分支的原因。
2PC痛点分析
策划师的两轮确认有个致命软肋。当厨师备好菜、占着灶台等待“开席”指令时,他推掉了其他订单;如果策划师宣布完“准备开席”后手机突然没电,所有供应商都不知道该不该开工——全场傻等。更糟的是,策划师要是永远联系不上,这场婚宴就永远卡在半路。
这就是 2PC 最著名的痛点——同步阻塞。准备阶段锁定资源后,参与者必须一直持有锁直到收到最终指令,期间其他事务无法访问这些数据。参与者越多、单点越慢,整体等待时间越长,事务吞吐随之大幅下降。
第二个痛点是单点故障:协调者是唯一的决策者,它在提交阶段宕机,所有参与者都无法自行决定提交还是回滚,只能阻塞等待其恢复;这期间系统处于“半瘫”状态。第三个痛点是脑裂与数据不一致:如果协调者在发送 commit 给一部分参与者后宕机,先收到 commit 的参与者已提交,其余参与者还停在 PREPARED——两边状态不一致,必须靠人工核对或额外的恢复协议补救。
所以说,2PC 的可靠性很大程度押在协调者这一个人身上——总策划不能生病、不能失联,甚至不能“漏发指令”。这正是它让架构师又爱又怕的原因。
- p_i每个参与者出错的概率,相当于每家供应商“放鸽子”的概率。
- ∏(1-p_i)所有参与者同时正常工作的概率,供应商越多,乘积越小。
- 1 - ∏用 1 减去“全部正常”的概率,得到“至少一个人掉链子”的概率。
- ≈ n·p̄当每个节点的故障概率为 p 时,整体失败概率大约是 n 倍的 p,随节点数线性上升。
3PC改进方案
为了救“策划师失联”的场,有人发明了 3PC(三阶段提交)。它给婚宴加了一道“预签到”:第一轮,策划师先打电话问“你们确定愿意来吗?愿意我们才往下走”;得到全员确认后,第二轮才让大家开始备料、锁定档期;第三轮才正式宣布开席。同时,每家供应商人手一份应急手册:等不到指令超过一定时间,就自行取消。
3PC 在 2PC 中间插入 CanCommit 阶段,把原来的 prepare 拆成了 canCommit(询问是否有能力提交)和 preCommit(预提交:写日志、锁资源、回复 ready)两步。最关键的是引入了超时机制:参与者在 preCommit 之后若迟迟收不到 doCommit,会主动中止,而不是像 2PC 那样无限阻塞。
完整流程是:①CanCommit——协调者询问“能否提交”,全员 YES 才继续;②PreCommit——协调者广播预提交,参与者写日志、锁资源并确认;③DoCommit——协调者广播最终提交,参与者正式提交。若协调者在 preCommit 后宕机,参与者等待超时后按“多数派规则”自行决定继续或中止,尽量恢复可用。
3PC 用“多一轮沟通”换来了“不怕失联”,但它并没有完美收官:多数派决定中止时,少数已提交的节点仍会不一致,只是概率和影响窗口被缩小了。婚宴上多加一轮确认降低风险,但隐患并没有从根上消失。
- CanCommit第一轮只询问“能力”,不锁任何资源,相当于先问“这周六能来吗”,成本为零。
- PreCommit第二轮写日志、锁资源并到达“预提交”状态,相当于备好菜、原地待命。
- DoCommit第三轮正式提交,相当于宣布开席。
- 超时 ⇒ 多数派兜底参与者等太久就按多数派规则自主决定,不再无限阻塞,这是 3PC 最重要的改进。
TCC柔性事务
如果婚宴实在等不起“全场同步”,还可以换一种思路:订金制。每家供应商先收订金、锁住档期(这叫预留);到了日子确认支付尾款、正式开工(这叫确认);如果有人临时反悔,退还订金、取消订单(这叫冲正)。三家不一定在同一秒完成,但最后账目一定是平的。
这个模式在软件里叫 TCC(Try-Confirm-Cancel),是一种业务层的柔性事务方案。它的三个阶段是:Try——只做资源预留和业务校验,不真正改动业务数据,比如冻结账户金额、预占库存;Confirm——确认业务真正生效,比如扣款、扣库存;Cancel——冲正 Try 的预留,比如解冻金额、释放库存。
TCC 没有全局锁、没有同步阻塞,每个参与者独立提交本地事务,靠业务侧的状态机和补偿操作收敛到最终一致——属于 AP 派方案。代价是:每个业务操作都要额外编写 Try/Confirm/Cancel 三套逻辑,还要保证幂等(Confirm 和 Cancel 可能被重试);开发成本和无 Bug 难度明显高于 2PC。业界常用的 Seata TCC、TCC-Transaction 都是这个思路的落地框架。
如果说 2PC 是“总策划全权指挥”,TCC 就是“各家自律 + 事后对账”:牺牲了一点实时强一致,换来了不会因为一个人卡死全局的弹性。具体选谁,取决于你对“同时”有多较真,对“晚一点平账”有多宽容。
- Try(预留)只做校验和占位,不改变最终业务数据,相当于收订金、锁档期。
- Confirm(确认)业务真正生效,相当于全款到账、正式开工。
- Cancel(冲正)撤销 Try 的预留,相当于退还订金、取消订单。
- ∨(最终二选一)每个参与者在 Confirm 与 Cancel 中必居其一,最终账目收敛一致,但不是同一秒强一致。
工程选型考量
回到婚宴本身:如果客人只有一桌,根本不需要跨国供应商,本地办一场就完事——单库事务足够;如果非跨国不可,又不能容忍任何闪失,就接受“全场等待”的 2PC;如果怕一个人卡死全局,就用 3PC;如果想保持高吞吐、也愿意写补偿代码,就上 TCC。没有最好的方案,只有最合适的选择。
工程选型的本质,是在一致性强度、性能开销、开发复杂度三者之间权衡:2PC 适合强一致性要求极高、跨库分支少(通常不超过 5 个)、并发量可控的核心链路,比如账户扣款、库存预占;TCC、本地消息表、事务消息适合允许最终一致、又需要较高吞吐的场景,比如订单、积分、通知类业务。
具体到业界实践:数据库 XA 协议(基于 2PC)在高并发下锁等待时间长,已经越来越少见;更常见的组合是 Seata 的 AT 模式(对 2PC 的改良,自动回滚)、TCC 框架、RocketMQ 事务消息,以及最朴素的“本地事务 + 异步对账”。无论选哪种,幂等设计、重试机制、日志落盘、对账任务都是绕不开的工程底座。
一句话总结:分布式事务没有银弹。你选择的不只是一个协议,更是一整套关于“等多久、谁负责、出错了怎么赔”的承诺书——理解了 2PC,就理解了为什么这些承诺书每一本都写着代价。
- 2 × RTT两轮网络往返(准备请求+投票、提交指令+确认),每一轮都占用事务关键路径。
- 2 × T_fsyncprepare 日志和 commit 日志各一次强制刷盘,保证故障后可恢复。
- P ∝ n参与者数量 n 越大,任一出错导致整体回滚的概率越高,所以 2PC 不适合大规模分片。
AI 应用场景
跨行转账与核心账务一致性
阶段:数据链路一致性保障(资金数据层面)
解决的问题:传统单库事务只能保证一个库内原子,跨行或跨库的“扣款+入账”一旦第二步失败,就会出现资金差账;AI 风控系统的对账任务每天要花大量人力修复这些长短款。2PC 让扣款库与流水库在同一事务中同时提交或同时回滚,从源头消除差账。
具体案例:某支付平台的核心账务系统,使用基于 XA(2PC 的数据库实现)的分布式事务管理用户账户库与交易流水库。用户发起转账时,两个库先进入准备阶段(写日志、锁账户行),全员通过后统一提交;某次网络抖动导致流水库准备超时,账户库自动回滚,资金守恒由日志和日终对账双重兜底。
电商订单与库存的强一致扣减
阶段:在线业务链路一致性保障(订单/库存数据)
解决的问题:订单库与库存库分离后,若先扣库存后下单失败,会产生“有单无货”;若先下单后扣库存,又可能超卖。2PC 将“下单成功”与“库存扣减”绑定为同一个原子操作。
具体案例:某电商平台的限量商品购买链路,订单中心和库存中心通过 Seata AT 模式(2PC 的改良实现)协同:下单事务同时对两个库加锁并准备,全部通过后统一提交;库存不足时整体回滚,超卖率降为零。大促场景下,同一团队将热点商品的库存扣减改为 TCC 预留,既保证不超卖又提升了吞吐。
Python 演示
# -*- coding: utf-8 -*-"""两阶段提交(2PC)最小模拟: 协调者 + 两个参与者(银行账户)""" class Participant: """参与者: 一个数据库节点, 管理一个账户余额""" def __init__(self, name, balance): self.name = name self.balance = balance self.frozen = 0 # 已冻结(预留)的金额 self.pending = None # 待生效的金额变更 def prepare(self, delta): """准备阶段: 校验余额并冻结金额, 返回是否同意提交""" if self.balance + self.frozen + delta < 0: return False self.pending = delta self.frozen += delta return True def commit(self): """提交阶段: 让冻结的变更正式生效""" if self.pending is None: return self.balance += self.pending self.frozen -= self.pending self.pending = None def rollback(self): """中止阶段: 释放冻结, 回到 prepare 前的状态""" if self.pending is None: return self.frozen -= self.pending self.pending = None class Coordinator: """协调者: 执行两阶段提交协议""" def __init__(self, participants): self.participants = participants def two_phase_commit(self, deltas): """deltas: 每个参与者对应的金额变更(如 -200 / +200)""" # 阶段一: 准备阶段, 向全体征询 print("== 阶段一: 准备(Prepare) ==") for p, d in zip(self.participants, deltas): ok = p.prepare(d) print(" {} 投票: {}".format(p.name, "YES" if ok else "NO")) if not ok: print(" 存在NO票, 发起中止(Abort)") for q in self.participants: q.rollback() return False # 阶段二: 提交阶段, 全员通过则统一提交 print("== 阶段二: 提交(Commit) ==") for p in self.participants: p.commit() print(" {} 已提交".format(p.name)) return True def show(participants): total = 0 for p in participants: total += p.balance print(" {} 余额 = {}".format(p.name, p.balance)) print(" 合计 = {} (守恒校验)".format(total)) def main(): alice = Participant("账户A", 500) bob = Participant("账户B", 300) coord = Coordinator([alice, bob]) print("转账 200: A -> B") ok = coord.two_phase_commit([-200, 200]) print("结果:", "成功" if ok else "回滚") show([alice, bob]) print("---") print("再转账 800: A -> B (余额不足, 应整体回滚)") ok = coord.two_phase_commit([-800, 800]) print("结果:", "成功" if ok else "回滚") show([alice, bob]) if __name__ == "__main__": main()
def prepare(self, delta):参与者完成准备阶段:校验余额并冻结金额,返回是否同意提交。if self.balance + self.frozen + delta < 0:余额不足直接投 NO 票,这是触发整体回滚的唯一入口。self.pending = delta只记下待生效变更,不立即改余额,模拟 prepare 阶段“只预备、不生效”。for p, d in zip(self.participants, deltas):协调者逐个征询参与者,严格按协议顺序推进。if not ok:只要有一个 NO,立即终止准备阶段,不再询问后面的参与者。q.rollback()广播回滚,让所有已 PREPARED 的参与者释放冻结资源。p.commit()全员通过后统一提交,保证“全有或全无”。if __name__ == "__main__":脚本可直接运行,演示转账成功与余额不足回滚两种结局。
常见误区
误区我学完 2PC 觉得它很完美:准备阶段问一遍、提交阶段通知一遍,数据不就永远一致了吗?协调者出点小问题应该也能自动恢复吧?
正解2PC 只在“无故障”或“故障可恢复”的前提下保证原子性。如果协调者在提交阶段宕机,一部分参与者已收到 commit、另一部分没有,两边就会出现不一致,且协议本身无法自动修复,必须靠人工核对或额外的恢复机制。它更像婚宴上的总策划:策划师在宣布“开席”的瞬间倒下,就一定会有人开席、有人没开。
为何常见:教材和文章通常先讲“正常流程”,把最棘手的协调者宕机场景放在最后,初学者容易把“设计目标”误当成“铁定保证”。
误区我以为 prepare 成功就等于本地事务已经提交了,那锁自然可以释放掉,等协调者让提交的时候再处理一下就行。
正解prepare 成功只是“预备”,最终状态还没定。参与者必须一直持有锁并等待协调者的最终指令,期间不能释放,否则其他事务可能读到未定型的半成品数据,破坏隔离性和原子性。这就是 2PC 同步阻塞的根源——厨师备好菜,但锅和灶台必须一直占着。
为何常见:把 prepare 类比成“本地提交”是最大的误解来源,实际上 prepare 只是“承诺可以提交”。
误区3PC 又有询问阶段又有超时机制,参与者不会再傻等了,那它应该完全替代 2PC,数据也绝对不会不一致了。
正解3PC 缩小了“等待窗口”和“不一致窗口”,但并没有消灭它们。协调者在 preCommit 后宕机,参与者超时后按多数派自行决定,仍可能与少数已提交的节点不一致;只是概率更低、恢复更快。它还把正常路径的延迟从两轮增加到了三轮,并非免费午餐。
为何常见:看到“改进”两个字就默认是“完美版”,忽略了分布式系统的根本限制——网络分区下,任何协议都做不到绝对一致和高可用兼得。
误区TCC 也是 Try、也是 Confirm/Cancel,跟 2PC 的准备、提交不是一回事吗?我写 TCC 的时候把它当成 2PC 来用。
正解两者本质不同:2PC 是数据库协议层的强一致方案,有全局锁、有同步阻塞,参与者被动执行;TCC 是业务层的柔性事务,每个操作独立提交本地事务,靠 Try 预留、Confirm 执行、Cancel 冲正,配合幂等重试达到最终一致。TCC 没有全局锁,但也因此失去了实时强一致。
为何常见:两者都有“先准备、后决定”的外形,容易让人只看表面流程,忽略一致性模型的根本差异。
面试题
Q1. 两阶段提交(2PC)的第一个阶段叫什么?
- A.准备阶段← 正确答案
- B.提交阶段
- C.预提交阶段
- D.回滚阶段
答案:A。2PC 的第一个阶段是准备阶段(Prepare),协调者向参与者征询“能否提交”;第二个阶段才是提交或回滚。预提交阶段是 3PC 的概念,属于干扰项。
Q2. 在 2PC 的准备阶段,参与者需要完成哪些工作?
- A.直接提交本地事务即可
- B.执行事务写日志并锁资源← 正确答案
- C.释放所有锁并返回投票
- D.等待协调者发送准备请求
答案:B。准备阶段参与者要把事务执行到可提交状态,写 undo/redo 日志并锁住资源,之后才能回复 YES/NO。“直接提交”和“释放锁”都会破坏原子性,而“等待准备请求”是协调者的职责,不是参与者的动作。
Q3. 当 2PC 的协调者收到全部参与者的 YES 投票时,会执行什么操作?
- A.向所有参与者广播中止
- B.向所有参与者广播提交← 正确答案
- C.忽略掉所有参与者的投票
- D.重新发起一个新的准备阶段
答案:B。全员 YES 是提交的唯一充要条件,协调者就此广播 Commit。只要存在一个 NO 就会广播 Abort;忽略投票和重新准备都不在协议流程中。就像婚宴上所有供应商都确认能到,总策划才宣布开席。
Q4. 以下哪一项是 2PC 最典型的工程痛点?
- A.实现过于复杂难以落地
- B.同步阻塞与协调者单点故障← 正确答案
- C.无法保证事务的原子性
- D.只能应用在单机数据库
答案:B。2PC 准备阶段要持锁等待、协调者宕机则全员卡死,这两点分别对应同步阻塞与单点故障。它实现简单(A 错)、原子性有保障(C 错)、也能跨数据库使用(D 错)。
Q5. 3PC 相比 2PC 最主要的改进是什么?
- A.减少了网络交互的总轮次
- B.引入询问阶段与超时兜底← 正确答案
- C.彻底去掉了协调者角色
- D.取消所有阶段的资源锁定
答案:B。3PC 增加 CanCommit 询问阶段,并给参与者设置超时兜底,缩小阻塞窗口。它轮次更多而非更少(A 错)、仍需要协调者(C 错)、preCommit 阶段依然要锁资源(D 错)。
Q6. 在经典 2PC 中,协调者在提交阶段宕机,已处于 PREPARED 的参与者会?
- A.立即自行提交本地事务
- B.立即自行回滚本地事务
- C.阻塞等待协调者恢复← 正确答案
- D.与其它参与者协商后处理
答案:C。经典 2PC 的参与者无法区分“协调者已发 commit 但我没收到”和“协调者还没发 commit”两种情况,只能阻塞等待。A 和 B 都可能破坏一致性,D 在 2PC 中不存在节点间的协商机制。
Q7. 关于 2PC 与 TCC 的对比,下列说法正确的是?
- A.两者都属于数据库协议层方案
- B.TCC 没有全局锁但需业务补偿← 正确答案
- C.2PC 的全局锁不会影响吞吐
- D.TCC 能提供实时的强一致性
答案:B。TCC 是业务层方案(A 错),Try 只做资源预留、无全局锁,并发更好,但要写 Try/Confirm/Cancel 三套逻辑并保证幂等。2PC 持锁时间长、吞吐低(C 错);TCC 是最终一致而非实时强一致(D 错)。
Q8. 2PC 在哪些异常场景下可能无法保证强一致?
- A.协调者在提交阶段宕机← 正确答案
- B.网络分区使部分节点失联← 正确答案
- C.参与者准备阶段投票反对
- D.所有节点正常且网络稳定
答案:AB。协调者宕机或网络分区会让部分节点收到 commit、部分收不到,从而卡在中间状态,可能不一致。C 会触发整体回滚,是协议内的正常路径;D 是正常场景,能保证强一致。
Q9. 下列哪些是 2PC 参与者的合法状态?
- A.正在执行工作处理← 正确答案
- B.已准备就绪待命← 正确答案
- C.已提交或已中止← 正确答案
- D.等待投票决定
答案:ABC。参与者状态机为 WORKING → PREPARED → COMMITTED/ABORTED。“等待投票决定”不是协议中的正式状态,参与者的投票行为发生在进入 PREPARED 之前,属于干扰项。
Q10. 关于分布式事务方案的选择,下列说法正确的是?
- A.两阶段提交属于强一致性方案← 正确答案
- B.TCC 属于最终一致性方案← 正确答案
- C.最终一致可选用TCC或消息← 正确答案
- D.三阶段提交可以彻底解决分歧
答案:ABC。2PC 是强一致、TCC 是最终一致;允许最终一致时优先选 TCC、本地消息表或事务消息以换取吞吐。3PC 只是缩小不一致窗口,并不能彻底解决网络分区下的脑裂问题,D 错。