Skip to content

跨分片事务与原子提交 ​

两个账户位于不同分片,从 A 转 10 元到 B 要求扣款与加款一起发生。即使每个分片内部都使用 Raft,它们各自的日志顺序也不会自动变成一个跨组原子决策。

先区分三个概念 ​

  • 共识:多个副本对同一组决策达成一致。
  • 原子提交:参与者对一个事务统一选择 commit 或 abort。
  • 隔离:并发事务的读写如何相互影响。

Raft 可以复制某个组的状态;2PC 协调参与者共同提交;锁、MVCC 或时间戳控制并发可见性。这些机制能组合,但不能互相替代。

两阶段提交 ​

第一阶段 prepare:协调者询问每个参与者能否提交。参与者检查约束、获取必要锁或写入意向,把“已准备”状态和恢复所需信息持久化后回复 yes;不能持久化承诺却先发 yes。

第二阶段 decision:只有全部 yes,协调者才记录 commit 决策并通知;否则记录 abort。参与者收到最终决策后提交或撤销,并释放资源。最终消息可能重发,所以处理必须幂等。

sequenceDiagram
  participant C as 协调者
  participant A as 分片 A
  participant B as 分片 B
  C->>A: Prepare(tx)
  C->>B: Prepare(tx)
  A-->>C: Yes,已持久化准备
  B-->>C: Yes,已持久化准备
  C->>C: 持久化 Commit 决策
  C->>A: Commit(tx)
  C->>B: Commit(tx)
查看流程图文本
sequenceDiagram
  participant C as 协调者
  participant A as 分片 A
  participant B as 分片 B
  C->>A: Prepare(tx)
  C->>B: Prepare(tx)
  A-->>C: Yes,已持久化准备
  B-->>C: Yes,已持久化准备
  C->>C: 持久化 Commit 决策
  C->>A: Commit(tx)
  C->>B: Commit(tx)

为什么 prepared 之后不能自行超时回滚 ​

假设 A、B 都投 yes,协调者把 commit 告诉 A 后宕机,B 尚未收到。B 若仅因超时自行 abort,就与已经提交的 A 分裂。因此 prepared 参与者可能需要等待可恢复的决策,这就是经典 2PC 的阻塞问题。把协调者状态复制到一个共识组可以改善单协调者故障,但仍依赖相关组的可用性和协议边界。

如果参与者还没承诺 yes,它可以按协议中止;进入 prepared 后就已经交出了单方面改变最终结果的自由。这条状态边界比“分两阶段”四个字更重要。

隔离性仍然需要设计 ​

两个事务都读取“当前有两名值班医生”,各自把自己改为不值班。在快照隔离下它们修改不同记录,可能都成功,最后无人值班,形成 write skew。即使每个事务的多分片提交都原子完成,也没有自动满足业务约束。

可串行化要求并发结果等价于某个串行顺序;线性一致性主要谈操作历史与真实时间,两个概念不要混用。“严格可串行化”还要求事务序列尊重实时先后。

实践中的另一种选择:Saga ​

长流程中长时间持锁代价很大,可以把工作拆为多个局部事务,并设计补偿动作。补偿不是时间倒流:已经发出的邮件无法从收件箱抹去,已发生的外部行为也未必完全可逆。Saga 通常向业务暴露中间状态与失败补偿,适合与产品语义一起设计。

本手册不实现完整跨组事务引擎;核心训练是画出每个节点的持久状态,逐条删掉消息并判断还能否自行决策。这个方法也可用于审查分片迁移、配置发布和作业调度。

自测:每个分片都强一致,跨分片读取一定看到同一时刻快照吗?

不一定。客户端先读 A、后读 B,期间有事务改变两者,可能混合不同时间点。全局快照需要时间戳、事务协调或其他协议明确建立,单组强一致不足以推导它。

来源:Gray 与 Lamport:Consensus on Transaction Commit、MIT 6.5840。