分布式事务

第一性原理:事务能力的构成

两块机制

事务不是数据库特性,而是一种状态一致性约束机制。

事务能力由两块机制供给:

机制族 根本问题 ACID 投影
Recovery 故障下,状态的定义如何保持明确 A、D
Concurrency Control 并发交错下,可见性如何受控 I
(应用契约,非机制) 业务不变式是否成立 C

A 与 D 同源:共用日志 + 恢复算法。undo 支撑 A,redo 支撑 D,差别只在故障时点与观察视角。

两块机制共同依赖一个东西:状态变更的全序。恢复需要它判定“哪些已生效”,并发控制需要它判定“谁看得见谁”。

分布式的唯一损失:全序退化为偏序

CAP/FLP理论(详见分布式理论分布式共识算法), 本篇文档聚焦:当”事务”这一抽象遇到分布式环境时,发生了什么质变。

单机不争取全序,它天然拥有:单一 WAL 的 LSN 序给出变更全序,单一发号器给出可见性基准。同一个全序同时供养 A 和 I,二者搭同一班车,因此单机语境几乎无需区分。

分布式只有偏序。这一个损失,把合一的机制劈成三个代价迥异的问题:

投影 是否仍可获得 代价
A 退化为共识问题
I 需把全序买回来 需全局排序设施
D 未退化,但边界收缩 幂等 + 至少一次投递(Outbox)

原子提交 ≡ 共识。这一等价使 FLP 与 CAP 的约束直接作用于事务提交——2PC 的阻塞由此成为推论。

A 未被打破,只是被重新定价:多数业务放弃它出于成本,不出于不可能。

D 的变化是边界收缩。 耐久度没降,变的是覆盖范围:单机持久化天然对所有参与者生效(共享存储),分布式下只覆盖本地——数据落盘了,但别的服务不知道。

于是持久化对象必须从状态扩到意图:不仅存”订单已付款”,还要存”需通知库存服务”这个待办。Outbox 就是把这个待办也纳入事务,让持久性边界重新覆盖跨服务通知。

代价:幂等由可选变强制(重发必然发生),且多出单机没有的中间态——已本地提交、尚未对外可见。

推导出的本质矛盾

”原子性”要求操作不可分割;
“分布式”本质上就是将操作分布到多个独立决策点。

二者的交集需要一个全局全序,而分布式恰好不提供它。

模式层:对全序的四种取舍

取舍 手法 代价 代表
模拟全序 协议内构造单一决策点 可用性(阻塞、协调者单点) 2PC / XA
购买全序 引入全局排序设施 部署成本与延迟 TSO、TrueTime 类
放弃 + 语义补偿 向前追加撤销语义 A 降为近似,I 失守 Saga / TCC
放弃 + 收敛 异步驱动状态机 仅保证最终一致 事务消息

真正不可逆的是 I。 补偿能让最终结果收敛到 all-or-nothing 的语义等价物,却无法让别人没看见过中间态。A 有成熟套路(补偿 + 幂等 + 重试 + DLQ),I 无通用解——只能按场景在应用层逐一打补丁(见下文工程对策)。

隔离性随“放弃全序的程度”单调递减——中间态如何暴露,决定 I 的存亡:

方案 中间态形态 I
2PC / XA 阻塞到决议,不外显
TCC 预留态:设计出的合法业务态 业务保证
Saga 真实中间态直接外显
消息事务 不加控制

TCC 以更高业务侵入(每个资源都要设计预留语义)换回部分 I;Saga 暴露真实中间态;消息事务彻底放弃。

隔离性与补偿的工程对策

I 无通用解——上文四种取舍中,凡“放弃全序”的方案都要在应用层逐一打补丁。常见手法:

手法 机制 代价
语义锁 中间态标记 PENDING,阻止他人误读 引入应用层锁,退化出部分阻塞
交换律更新 Append-Only / 增量操作,消除写冲突 仅适用可交换语义
悲观视图 未决数据对外不可见 牺牲时效与可用性
重读校验 提交前重读比对(OCC 式) 冲突高时反复重试

这些只能压低脏读概率、缩小暴露窗口,无法根除中间态可见——I 的失守是放弃全序的必然代价,工程上只能控制而非消除。

横向同构:全序稀缺是跨尺度母题

尺度 全序设施 放弃后的形态
单核 程序顺序
多核 CPU 总线序 / 缓存一致性协议 弱内存模型 + 内存屏障按需重建
单机多库 2PC 模拟
跨服务 Saga 补偿 / 事件收敛
事件溯源 分区内序 因果序 + 幂等消费

不变规律:每次系统尺度扩大,全序都变得更贵;应对手法始终只有三种——模拟它、购买它、或放弃它并在语义层重建。

这一取舍框架决定了后续所有方案的设计哲学。

分布式事务抽象模型

方案的本质分类:决策粒度

所有分布式事务方案,按决策的原子单位可归为两类:

单决策协调:就一个结果达成一致

决策对象:COMMIT 还是 ABORT,一个比特。

抽象模型:

所有参与者 → 投票 → 协调者汇总 → 广播统一决策

核心约束:

典型:2PC、XA。适合金融核心账务、低并发强一致场景

本质:这是共识问题在事务上的应用。一旦映射到共识,FLP/CAP 的约束自动生效——阻塞或放弃一致性,二选一。

多步骤编排:协调一串操作的执行与撤销

决策对象:每一步是否继续,以及失败后如何补偿,N 个决策。

抽象模型:

步骤1 → 步骤2 → ... → 步骤N
  ↓       ↓             ↓
补偿1   补偿2         补偿N

核心约束:

典型:Saga、TCC、事务消息

本质:这是工作流编排问题。与 Kubernetes Job、AWS Step Functions、BPMN 同构——都在管理"多步骤如何在失败时安全回退"。

TCC:每个参与方提供三阶段接口:

关键问题:

适合短流程、资源可预留、需较强隔离的账务类操作。

Saga:正向事务序列 + 逆序补偿链。中间态外显,需业务建模。适合长事务、核心交易但需高可用场景。

消息驱动最终一致性:

模式 机制 适用
事务消息 MQ 提供事务语义,本地事务与发消息绑定 同一组织内
本地消息表 消息作为数据写入业务库,轮询/CDC 投递 无事务 MQ 时
最大努力通知 重试 N 次后放弃,接收方主动查询兜底 跨组织、跨网络

统一抽象:本地事务保证状态落盘 → 消息保证状态传播 → 重试保证收敛。

分类的稳定性

维度 单决策协调 多步骤编排
问题映射 共识(Consensus) 编排(Orchestration)
时间尺度 瞬时(ms-s) 长期(s-min-h)
失败处理 回滚(UNDO) 补偿(Compensation)
理论工具 分布式系统理论 工作流理论

治理视角:一致性责任的组织化

为什么必然出现治理层

分布式事务把一致性保证从数据库自动兜底下沉为应用显式维护。三个质变随之发生:

维度 单机 分布式
一致性 系统属性,自动保证 运营对象,需持续维护
不一致 异常,不应出现 常态,存在可接受窗口(soft state)
兜底责任 存储层 / DBA 业务团队

系统不再自动兜底,组织必须补上。治理层不是附加运维,而是用组织流程重新实现数据库放弃的那部分一致性

治理的本质:检测—收敛闭环

补偿只覆盖已知失败路径,无法处理未知漂移(消息丢失、补偿自身失败、代码 bug、状态损坏)。因此需要一个独立于事务链路的闭环——检测不一致 → 收敛不一致,分层防御,自动优先、人工兜底:

职责 核心设施 关键指标
可观测 发现卡单与漂移 事务日志 / 状态机 + 全局 correlation ID 卡单率、补偿失败率、不一致窗口时长
自动收敛 消化已知失败 重试 + 死信队列 + 定时补偿 重试成功率、DLQ 堆积量
对账 兜住未知漂移 独立于主链路的业务级校验 对账差异数、修复时延
人工兜底 处理无法自愈 告警 + runbook + SLA 人工介入次数、MTTR

对账:比补偿更底层的安全网

补偿 对账
触发 事务链路内,失败即触发 独立周期运行
覆盖 已知失败路径 任意来源的最终不一致
依赖 依赖链路本身正常 不依赖链路,直接比对终态

补偿失败、消息丢失、程序缺陷造成的漂移,只有对账能发现并修复。补偿处理“已知的失败”,对账兜住“未知的漂移”——后者是前者的最后一道防线。

组织维度:Conway 定律的投影

补偿链与对账逻辑天然跨服务,而服务边界即团队边界。由此派生的都不是纯技术问题:

这是“系统越分布,事务越业务化”的组织投影:一致性的 C 从数据库内的约束,变成跨团队协作的契约。

稳定内核

  1. 事务能力 = 恢复 + 并发控制,二者共同依赖**状态变更的全序**
  2. 分布式的唯一损失:全序退化为偏序,把合一的机制劈成三个代价迥异的问题
    • A → 退化为共识(原子提交 ≡ 共识)
    • I → 无通用解,只能在应用层逐一打补丁
    • D → 未退化,但边界收缩(Outbox)
  3. **真正不可逆的是 I**:补偿能补回 A,补不回“别人已看见中间态”
  4. 方案按**决策粒度**分两类:单决策协调(共识)/ 多步骤编排(编排)
  5. 一致性责任下沉到组织:补偿处理已知失败,对账兜住未知漂移

关联内容(自动生成)