想象一下,你要组织一场 200 人的公司团建聚餐。你作为协调者,挨个打电话问各个部门的同事:“周五晚上有空吗?能参加吗?”有人爽快答应,有人支支吾吾,有人直接说“不行”。如果所有人都说“有空”,你就能在群里宣布“定了,周五晚 6 点老地方见!”;只要有一个人说不行,这场聚餐就得重新安排。问题来了——如果你在问完所有人之后、正式宣布之前,手机突然没电关机了,那所有人都在等你的最终通知,这顿饭到底吃还是不吃?谁都不敢先走,也不敢提前安排别的事。这就是分布式事务面临的核心难题,也正是两阶段提交(2PC)和三阶段提交(3PC)协议想要解决的问题。

岗位关联

掌握 2PC/3PC 协议,能帮助你在设计跨库事务、微服务数据一致性方案时做出正确权衡——这是架构师和高级后端工程师绕不开的核心知识。

目录
  1. 可视化演示
  2. 事务基础——为什么聚餐需要规则
  3. 二阶段原理——先问有没有空,再宣布开席
  4. 2PC异常——小A失联了
  5. 三阶段提交——多加一步“预通知”
  6. 对比分析——两种组织方式的账本
  7. 工程实践——真实世界里的分布式事务
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

模拟协调者与参与者间的两阶段提交与三阶段提交消息交互,观察阻塞问题与超时降级机制,理解原子提交的折中设计。

本节配有交互动效(进入视口后自动加载,约 3KB)。

模式1:2PC 两阶段提交 协调者先发 Prepare 征询 全部 Yes 后广播 Commit msgs = 2N 任一 No 或超时则整体 Abort,保证原子性。

事务基础——为什么聚餐需要规则

小A最近接手了一个大工程:组织一场全公司 200 人的团建聚餐。这可不是拉个群发个公告那么简单——有人海鲜过敏,有人出差在外地,有人手头项目要加班。小A发现,只有当每个人手里的小算盘(本地事务)都协调好,这顿“全局聚餐”才能顺利开席。他需要一套明确的规则,让分散在各处的同事在同一个目标上达成一致。

在分布式系统中,一个业务操作往往被拆成多个子操作,运行在不同的数据库或服务实例上。例如电商下单流程中,库存服务和账户服务分别位于不同的节点。每个节点都有自己的本地事务,但如果只保证本地事务独立成功,跨节点的一致性问题就会浮现:库存扣减了,但账户扣款失败了,数据就乱了。此时就需要“全局事务”把分散的本地事务协调起来,让它们对外呈现统一的 ACID 特性。

原子性(Atomicity)要求所有子事务要么全部成功、要么全部失败;一致性(Consistency)要求事务结束后数据满足所有约束;隔离性(Isolation)要求事务之间互不干扰;持久性(Durability)要求提交成功后数据不丢失。在分布式环境下,网络可能抖动、节点可能宕机,要让分散的节点共同守住 ACID,难度比单一数据库高出一个量级。

小A最终意识到,聚餐的规则本质上就是“要么大家一起吃,要么大家都不吃”。分布式事务协议,正是把这条朴素的规则变成一套可执行、可容错的工程方案。

Atomicity:⋀i=1nCommiti ∨ ⋀i=1nAborti\displaystyle \text{Atomicity:} \quad \bigwedge_{i=1}^{n} \text{Commit}_i \ \vee \ \bigwedge_{i=1}^{n} \text{Abort}_i
意思是:所有子事务一起提交,或者一起回滚,绝不允许出现“有的提交、有的回滚”的中间状态。就像聚餐要么全员到场开席,要么干脆取消,不能让 50 个人先吃、剩下 50 个人干等。
  1. 合取符号$\bigwedge_{i=1}^{n}$ 表示对 $1$ 到 $n$ 的所有下标取逻辑“且”,即每个子事务都必须满足某个条件。
  2. 子事务提交状态$\text{Commit}_i$ 表示第 $i$ 个节点上的子事务成功提交。
  3. 子事务回滚状态$\text{Abort}_i$ 表示第 $i$ 个节点上的子事务回滚。
  4. 析取连接$\vee$ 表示“或者”,公式的两个分支分别对应“全部提交”和“全部回滚”两种结局。

二阶段原理——先问有没有空,再宣布开席

小A决定用最直接的方式组织聚餐:挨个打电话问“周五晚有空吗?能参加吗?”(准备阶段)。如果每个人都回答“能”,他就在群里发正式通知:“定了,周五晚 6 点老地方见!”(提交阶段)。只要有一个同事说“不行”,小A就果断通知所有人“聚餐取消”。整个过程清晰、简单、符合直觉。

这个思路在分布式系统里就是两阶段提交协议(2PC,Two-Phase Commit)。系统中有两类角色:协调者(Coordinator) 和 参与者(Participant)。协调者负责发起事务并收集各方意见;参与者负责执行本地事务并汇报状态。整个流程分为两个阶段:第一阶段是准备阶段(Prepare),协调者向所有参与者发送 prepare 请求,参与者执行本地事务到可提交状态,比如锁定资源、写日志,然后回复“就绪(Ready)”或“中止(Abort)”;第二阶段是提交阶段(Commit/Abort),协调者汇总投票,如果全体就绪就发送 commit 指令,否则发送 abort 指令。

2PC 最大的特点就是“一人反对,全员回滚”。这种“全票通过”机制保证了原子性,但同时也意味着协调者掌握着最终裁决权。

小A的“全票通过”方案就是 2PC 的精髓:简单直接,容易理解。但他很快发现,这个方案在现实中有一个隐患——万一他打完电话、还没宣布结果的时候出了意外,所有人都会陷入尴尬的等待。

Commit2PC=⋀i=1nReadyi\displaystyle \text{Commit}_{2PC} = \bigwedge_{i=1}^{n} \text{Ready}_i
只有所有参与者都回复“就绪”,协调者才会下达“提交”总命令;只要有一票“中止”,整体事务就变成回滚。
  1. 最终提交判定$\text{Commit}_{2PC}$ 表示整个两阶段提交事务能否进入提交阶段。
  2. 就绪状态$\text{Ready}_i$ 表示第 $i$ 个参与者已经准备好本地资源并回复“就绪”。
  3. 全票通过$\bigwedge_{i=1}^{n}$ 要求所有参与者都满足条件,缺少任何一票都会导致整体中止。

2PC异常——小A失联了

小A打了一圈电话,每个同事都答应了“周五见”。他正准备在群里发“确定了!”的瞬间,手机突然没电关机。同事们全懵了:聚餐到底还办不办?没有人敢提前安排别的事,也不知道该不该去老地方。这就是 2PC 最著名的“阻塞(Blocking)”问题:协调者一挂,整个事务卡死。

具体来说,2PC 的协调者是单点。当协调者在发送 prepare 请求之后、收到全部回复之前崩溃,参与者已经执行了本地事务并持有锁,状态变为“不确定(UNCERTAIN)”。没有协调者的最终指令,参与者既不敢提交也不敢回滚,只能一直等下去。等待期间锁不释放,其他事务也被堵住,系统吞吐量大幅下降。如果协调者从此不再恢复,这些资源可能被锁住很久,甚至造成级联故障。

小A从此记住了教训:不能把全部希望押在一个人身上,必须设计一个“即使组织者失联,大家也知道该怎么办”的方案。

Blocking:∃i:Statei=UNCERTAIN ∧ Coordinator=Down\displaystyle \text{Blocking:} \quad \exists i: \text{State}_i = \text{UNCERTAIN} \ \wedge \ \text{Coordinator} = \text{Down}
只要有一个参与者处于“不确定”状态,且协调者崩溃,该参与者就会被卡住,无法继续推进事务。
  1. 存在量词$\exists i$ 表示“存在某个参与者 $i$”满足条件。
  2. 不确定状态$\text{State}_i = \text{UNCERTAIN}$ 表示这个参与者已经锁定了本地事务资源,但还没收到最终指令。
  3. 协调者故障$\text{Coordinator} = \text{Down}$ 表示协调者进程宕机或失联。
  4. 同时发生$\wedge$ 表示两个条件必须同时成立,才会触发阻塞。

三阶段提交——多加一步“预通知”

小A吸取教训,改变了组织方式。他先在企业微信群里发问卷:“周五晚聚餐,大家方便吗?”(CanCommit,能不能参与)。等所有人都回复“方便”后,小A进一步通知:“我已经预定了周五晚 6 点的包间,若有人临时有事请尽早说”(PreCommit,预提交)。过了一段缓冲时间,看没人反对,小A再发“确认!周五晚 6 点老地方见!”(DoCommit,最终提交)。就算他发出“预定通知”后手机又没电了,大家也知道“位置早就订好了,聚餐大概率照常”,可以自主决定继续或取消,不再被完全卡死。

这正是三阶段提交协议(3PC,Three-Phase Commit) 的核心思路。3PC 在 2PC 的基础上增加了一个 CanCommit 阶段,把原来的准备阶段拆成了两步:第一步 CanCommit,协调者只询问参与者“能不能提交”,参与者只做评估,不执行本地事务、不加锁;第二步 PreCommit,协调者收到全部“能”之后,再通知参与者预提交,此时才锁定资源、写预提交日志。最后是第三步 DoCommit,协调者确认无新问题后,全员正式提交。

3PC 的价值在于:当协调者在 PreCommit 之后失联时,参与者已经知道“协调者打算提交”,可以依据预提交的日志状态自行决定继续或中止,降低阻塞的概率。代价是协议多了一轮交互,消息复杂度和延迟都增加了。

小A的“先问卷、后预定、再确认”让团队在面对意外时有了更多自主权。但他也清楚,多一步沟通意味着多一分成本——这正是 2PC 和 3PC 之间的核心权衡。

Commit3PC=CanCommit ∧ ⋀i=1nPreCommiti\displaystyle \text{Commit}_{3PC} = \text{CanCommit} \ \wedge \ \bigwedge_{i=1}^{n} \text{PreCommit}_i
先确认“能不能干”,再通知大家“准备干”,最后才真正干。3PC 用多一轮沟通换来了故障时更多的决策信息。
  1. 能否参与$\text{CanCommit}$ 表示协调者收到了所有参与者“可以参与”的评估结果。
  2. 预提交完成$\text{PreCommit}_i$ 表示第 $i$ 个参与者已经完成预提交,锁定资源并写入预提交日志。
  3. 两级条件同时满足$\wedge$ 表示必须同时满足“全员可以参与”和“全员预提交完成”这两个条件,才能进入最终提交阶段。

对比分析——两种组织方式的账本

小A把两种组织方式放在一起算了一笔账。2PC 只有“打电话问”和“发通知”两步,沟通成本低、执行速度快;但一旦他失联,大家全部抓瞎。3PC 多了一道“问卷”和“预定通知”,沟通次数更多、等待时间更长;但即使他中途失联,大家也已经知道“聚餐基本定了”,信息更加充分。

从协议层面看,2PC 和 3PC 的差异体现在几个维度。消息复杂度上,假设参与者数量为 $n$,2PC 需要 $2n+1$ 条消息(第一轮 prepare 请求与投票回复,第二轮 commit/abort 指令),3PC 则要 $3n+1$ 条(多一轮 CanCommit 询问及响应)。延迟方面,2PC 需要约 2 个网络往返(RTT),3PC 需要 3 个 RTT。阻塞性上,2PC 在协调者故障时参与者完全阻塞;3PC 增加的预提交状态让参与者在协调者失联后也能做决策,从而降低阻塞概率。适用场景上,2PC 常用于传统数据库 XA 事务,强一致但效率低;3PC 理论上更优,但实现复杂,在工程中反而较少直接使用。

算完这笔账,小A点点头:不存在“绝对更好”的协议,只有“更适合当前场景”的选择。

M2PC=2n+1<M3PC=3n+1;L2PC=2RTT<L3PC=3RTT\displaystyle M_{2PC} = 2n + 1 < M_{3PC} = 3n + 1; \quad L_{2PC} = 2\text{RTT} < L_{3PC} = 3\text{RTT}
3PC 用多一轮网络交互换来了更低的阻塞风险。“多花钱,买个保险”就是这个道理。
  1. 消息次数$M_{2PC}$ 和 $M_{3PC}$ 分别表示两个协议在协调者与参与者之间发送的总消息条数。
  2. 通信复杂度$n$ 是参与者数量。2PC 两轮消息对应 $2n+1$,3PC 三轮消息对应 $3n+1$。
  3. 延迟对比$L_{2PC}$ 和 $L_{3PC}$ 表示完成协议所需的网络往返次数,3PC 多一次 RTT 意味着响应时间更长。

工程实践——真实世界里的分布式事务

真正的大公司团建,通常有专门的后勤小组和应急预案。小A不再自己单打独斗,而是引入企业级团建系统来做流程管理,甚至把大团建拆成若干个可以独立取消的小环节。回到技术世界,生产环境中的分布式事务也是同样的思路:2PC/3PC 是理论基础,但落地时往往需要结合实际场景做取舍。

在工程实践中,2PC 最经典的落地是 XA 协议。它由 X/Open 组织提出,主流数据库(如 MySQL、Oracle)都提供 XA 支持,适合跨库强一致性场景,比如跨行转账、金融结算。但 XA 存在明显的性能瓶颈:事务过程中资源全程加锁,吞吐量受限;协调者单点故障会导致参与者长时间阻塞。因此,越来越多的互联网公司选择柔性事务方案,如 TCC(Try-Confirm-Cancel)、Saga、可靠消息最终一致性。这些方案不追求强一致,而是通过补偿机制保证“最终一致”,适用于订单、支付、积分等对短暂不一致容忍度较高的业务。中间件方面,阿里开源的 Seata 提供了 AT、TCC、Saga 等多种模式,成为微服务分布式事务的主流选择。

无论采用哪种方案,都要配备超时重试、监控告警和补偿脚本。2PC/3PC 对协调者的依赖很大,所以工程上还要对协调者做高可用部署,避免单点故障。

小A终于明白:分布式事务不是“选一个协议就万事大吉”,而是一套结合了协议、中间件、监控和容错的系统工程。

P(commit)=∏i=1npi\displaystyle P(\text{commit}) = \prod_{i=1}^{n} p_i
假设每个参与者正常的概率是 $p_i$,那么整个事务能顺利提交的概率就是所有参与者可用性的乘积。节点越多,成功率越低——这提醒我们 2PC 不适合参与者过多的场景。
  1. 连乘符号$\prod_{i=1}^{n}$ 表示把第 $1$ 个到第 $n$ 个参与者的可用性相乘。
  2. 单个可用性$p_i$ 表示第 $i$ 个参与者正常的概率。
  3. 全局成功率$P(\text{commit})$ 表示整个事务顺利提交的概率。因为任何一个节点失败都会导致整体失败,所以概率是各节点成功率的乘积。

AI 应用场景

金融跨行转账:强一致性兜底

阶段:分布式事务设计

解决的问题:传统银行转账在多个账户系统之间流转时,A 行扣款和 B 行入账必须“同时成功或同时失败”。单靠人工核对或定时对账,无法满足资金安全要求,而 2PC/XA 协议可以在数据库层达成强一致。

具体案例:某支付平台在跨行转账接口中引入 MySQL XA。协调者先让 A 行账户服务 prepare(锁定扣款资源),再让 B 行入账服务 prepare(锁定入账资源),两者全部就绪后统一 commit,保证资金不丢失、不重复。配合协调者高可用和监控告警,将单点故障风险降到最低。

电商订单:从 2PC 到 Saga 的演进

阶段:微服务数据一致性保障

解决的问题:下单涉及订单服务、库存服务、优惠券服务和积分服务,横跨多个微服务和多个数据库。如果强行使用 2PC,全局锁和低吞吐会拖垮大促场景;但完全不用分布式事务又会出现“订单已创建、库存没扣成功”的脏数据。

具体案例:某电商平台早期用 2PC 保证下单链路强一致,后来遇到性能和故障恢复问题,改用 Seata 的 Saga 模式:先创建订单,再扣库存,再加积分;如果库存扣减失败,自动触发“取消订单”补偿操作。系统吞吐量提升数倍,短时不一致通过补偿后最终收敛。

数据库分库分表:跨库更新的一致性

阶段:数据架构治理

解决的问题:订单库按用户 ID 分片后,一笔订单的主记录和明细记录可能落在不同的分库中。分库分表后,原本简单的事务变成了跨库事务,必须依赖分布式事务协议来保证数据一致。

具体案例:某 Saas 平台将订单库拆分为 4 个分片后,引入 Seata AT 模式处理跨库写入。AT 模式通过拦截 SQL、生成反向 SQL(undo log)实现自动回滚,既兼容 2PC 的思路,又减少了对业务的侵入。当参与者分支失败时,回滚日志自动补偿,达到最终一致。

Python 演示

python80 行
"""2PC/3PC 提交协议模拟器"""import random  class Participant:    """参与者节点: 负责本地事务的执行与投票"""     def __init__(self, node_id, success_rate=1.0):        self.node_id = node_id        self.success_rate = success_rate        self.state = "UNKNOWN"     def can_commit(self):        """评估能否参与事务 (3PC CanCommit 阶段)"""        return random.random() < self.success_rate     def pre_commit(self):        """预提交: 锁定资源并记录日志 (3PC PreCommit 阶段)"""        self.state = "PRECOMMIT"     def do_commit(self):        """最终提交"""        self.state = "COMMITTED"     def do_abort(self):        """回滚事务"""        self.state = "ABORTED"  class Coordinator:    """协调者: 驱动整个提交协议流程"""     def __init__(self, participants, crash_on_commit=False):        self.participants = participants        self.crash_on_commit = crash_on_commit     def run_2pc(self):        """两阶段提交: Prepare + Commit/Abort"""        votes = [p.can_commit() for p in self.participants]        if not all(votes):            for p in self.participants:                p.do_abort()            return "ABORT"        if self.crash_on_commit:            return "BLOCKED"        for p in self.participants:            p.do_commit()        return "COMMIT"     def run_3pc(self):        """三阶段提交: CanCommit + PreCommit + DoCommit"""        if not all(p.can_commit() for p in self.participants):            for p in self.participants:                p.do_abort()            return "ABORT"        for p in self.participants:            p.pre_commit()        if self.crash_on_commit:            for p in self.participants:                if p.state == "PRECOMMIT":                    p.do_commit()            return "RECOVERED"        for p in self.participants:            p.do_commit()        return "COMMIT"  if __name__ == "__main__":    nodes = [Participant("节点" + str(i), 0.95) for i in range(3)]    c1 = Coordinator(nodes)    print("2PC 正常结果:", c1.run_2pc())    print("节点状态:", [p.state for p in nodes])     c2 = Coordinator(nodes, crash_on_commit=True)    print("2PC 协调者崩溃:", c2.run_2pc())    print("节点状态:", [p.state for p in nodes])     c3 = Coordinator(nodes, crash_on_commit=True)    print("3PC 协调者崩溃:", c3.run_3pc())    print("节点状态:", [p.state for p in nodes])
目的:用面向对象模拟 2PC 和 3PC 的完整执行流程,直观对比协调者崩溃时两种协议的行为差异。
  1. votes = [p.can_commit() for p in self.participants]协调者向每个参与者询问“能否提交”,收集所有投票结果。
  2. if not all(votes):只要有一票反对,整体事务就走回滚分支。
  3. if self.crash_on_commit:模拟协调者在准备完成后崩溃的场景。
  4. return "BLOCKED"2PC 中协调者崩溃后,参与者陷入阻塞等待。
  5. for p in self.participants: p.pre_commit()3PC 独有步骤:让所有参与者锁定资源并写预提交日志。
  6. if p.state == "PRECOMMIT": p.do_commit()3PC 在协调者失联后,参与者依据预提交状态自主提交,避免阻塞。

常见误区

误区很多初学者以为 3PC 比 2PC 多一个阶段,就彻底解决了阻塞,分布式事务从此高枕无忧。

正解3PC 只是通过增加 CanCommit 和 PreCommit 阶段,让参与者在协调者故障时获得更多决策信息,从而“降低”阻塞概率。但它并不能完全杜绝阻塞或数据不一致,尤其在网络分区(脑裂)时,不同参与者可能收到互相矛盾的信息,依然可能出现部分提交、部分回滚的尴尬局面。3PC 的非阻塞结论依赖于“不会出现两个节点同时故障”等理想假设。

为何常见:人容易从“协议升级”推出“问题全解决”的简单结论,而忽略了协议在理论边界上的严格适用条件。

误区不少人以为协调者一发 prepare 指令,参与者立刻就把数据写进去了,prepare 就是“预提交”。

正解在 2PC 的准备阶段,参与者只是把本地事务执行到“可提交”状态——锁定相关资源、写日志,然后把“Ready“或”Abort“回报给协调者。真正修改数据(提交)发生在第二阶段,只有收到协调者的 commit 指令后才会落盘。若把 prepare 误认为提交,就无法理解为什么参与者会处于”不确定”状态。

为何常见:“准备”和“提交”两个动词在日常语境中含义相近,容易混淆;加上 3PC 又引入了“预提交”术语,让初学者更加糊涂。

误区有些人通过对比消息量就断定 3PC 比 2PC 慢很多,认为生产环境里 3PC 完全不可用。

正解3PC 确实多了一轮 RTT 和 $n$ 条消息,但它换来了故障场景下更快的恢复速度和更低的阻塞风险。实际工程中,性能瓶颈通常不在协议本身的几条消息,而在于锁竞争、网络延迟和故障恢复开销。3PC 在工程中少用的主要原因不是“慢”,而是实现复杂度高、在极端分区场景下并不比 2PC 好太多。

为何常见:仅从算法复杂度的单一维度做对比,忽略了故障恢复和可用性带来的整体收益。

误区认为 2PC 全员投"Ready"就等于事务 100% 提交成功,不可能再出错。

正解“Ready“只表示”我这边目前可以提交“,并不保证提交过程一定顺利。参与者可能在提交执行时遇到磁盘写满、进程崩溃、网络中断等新故障,协调者也可能在发 commit 时宕机。任何协议都无法承诺”必定成功“,设计目标只是”在各类失败中尽量做到可恢复、可收敛”。

为何常见:把协议的设计意图“尽力提交”误当成“必定成功”,忽略了真实世界里无处不在的故障概率。

面试题

Q1. 2PC 协议的第一个阶段通常叫什么?

单选题 · easy

  • A.提交阶段
  • B.准备阶段← 正确答案
  • C.回滚阶段
  • D.预提交阶段

答案:B。2PC 分为准备阶段和提交阶段。准备阶段由协调者询问参与者能否提交,参与者执行到可提交状态并投票;提交阶段才根据投票结果决定 commit 或 abort。选项 D 的“预提交阶段”属于 3PC 的概念。

Q2. 2PC 中协调者的作用是什么?

单选题 · easy

  • A.执行所有参与者的本地事务
  • B.收集投票并做出最终决策← 正确答案
  • C.转换各节点的网络通信协议
  • D.保存并复制所有数据副本

答案:B。协调者是 2PC 的大脑,负责向参与者发送 prepare 请求、收集投票结果,并根据“是否全员就绪”决定最终提交或回滚。它不执行具体本地事务,也不负责数据存储。就像小A只负责问意见和做宣布,不替同事们吃饭。

Q3. 在 2PC 的准备阶段,参与者收到 prepare 请求后会做什么?

单选题 · mid

  • A.直接提交本地事务记录
  • B.准备到可提交状态并加锁← 正确答案
  • C.立即回滚本地事务记录
  • D.向客户端返回执行结果

答案:B。准备阶段的核心动作是“把本地事务执行到可提交状态”,包括锁定资源和写日志,但绝不落盘提交。参与者随后将 Ready 或 Abort 状态告知协调者。这个“锁定但未提交”的状态正是后续阻塞问题的根源。

Q4. 2PC 的阻塞问题最可能发生在哪种情况?

单选题 · mid

  • A.某参与者宕机无响应
  • B.协调者崩溃未发指令← 正确答案
  • C.网络延迟显著变大
  • D.客户端主动断开连接

答案:B。当协调者在准备阶段之后、下发 commit/abort 之前崩溃,参与者持有锁且处于不确定状态,既不能提交也不能回滚,只能干等。网络延迟大或参与者宕机通常不会永久阻塞,协调者故障才是 2PC 阻塞的核心场景。

Q5. 3PC 新增的 CanCommit 阶段发生在什么时候?

单选题 · mid

  • A.在 PreCommit 和 DoCommit 之间
  • B.在所有参与者执行本地事务之前← 正确答案
  • C.在协调者收到提交确认之后
  • D.在参与者全部完成提交之后

答案:B。CanCommit 是 3PC 的第一阶段,发生在任何本地事务执行之前。协调者先询问“你们能不能参与”,参与者只做可行性评估,不加锁。这样可以尽早筛掉无法完成的节点,避免 2PC 中“问完才发现有人不行”的浪费。

Q6. 3PC 的 PreCommit(预提交)阶段,参与者通常要做什么?

单选题 · mid

  • A.立即提交并释放所有锁
  • B.锁定资源并记录预提交日志← 正确答案
  • C.放弃对事务的控制权
  • D.与客户端协商超时时间

答案:B。PreCommit 相当于“预定包间并登记”。参与者锁定事务所需资源、写入预提交日志,然后等待协调者的最终指令。这个预提交状态为后续故障恢复提供了关键信息,让参与者能在协调者失联后自主决策。

Q7. 与 2PC 相比,3PC 的核心改进是什么?

单选题 · hard

  • A.完全消除了网络分区的可能
  • B.降低了协调者故障时的阻塞概率← 正确答案
  • C.让所有节点都能并行提交
  • D.不需要参与者确认就提交

答案:B。3PC 引入 CanCommit 和 PreCommit 后,参与者在协调者故障时知道“协调者已经打算提交”,可根据自身预提交状态决定继续或中止,从而降低完全阻塞的概率。但它并不能消除网络分区或保证全并行提交,分布式一致性的极限依然存在。

Q8. 2PC 在哪些情况下可能遇到困难?

多选题 · mid

  • A.协调者发送准备后崩溃← 正确答案
  • B.参与者迟迟未回复投票← 正确答案
  • C.所有参与者正常且协调者正常
  • D.某个参与者提交过程中宕机← 正确答案

答案:ABD。协调者崩溃会导致参与者阻塞;参与者投票超时会让协调者无法按时决策;提交过程中参与者宕机会造成部分节点已提交、部分未提交的不一致。C 是理想情况,当然不会遇到困难。

Q9. 关于 3PC,下列说法正确的有哪些?

多选题 · hard

  • A.引入了 CanCommit 阶段← 正确答案
  • B.增加了 PreCommit 阶段← 正确答案
  • C.任何场景下都不会发生阻塞
  • D.消息复杂度高于 2PC← 正确答案

答案:ABD。3PC 在 2PC 基础上新增 CanCommit 和 PreCommit 两个阶段,总消息数从 $2n+1$ 变为 $3n+1$,所以复杂度更高。但“任何场景下都不会阻塞”是错误结论——在网络分区等极端场景下,3PC 仍可能产生不一致或阻塞。

Q10. 以下哪些场景适合用 2PC/3PC 协议?

多选题 · easy

  • A.跨行转账强一致性← 正确答案
  • B.分库分表跨库更新← 正确答案
  • C.单机单进程本地事务
  • D.微服务间事务协调一致← 正确答案

答案:ABD。2PC/3PC 解决的是“跨多个节点”的分布式事务问题,跨行转账、分库更新和微服务协调都需要跨节点一致性,因此适用。单机单进程的本地事务用数据库自带事务就足够了,不需要引入分布式协议。