事务不是数据库特性,而是一种状态一致性约束机制。
事务能力由两块机制供给:
| 机制族 | 根本问题 | 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 |
| 补偿 | 对账 | |
|---|---|---|
| 触发 | 事务链路内,失败即触发 | 独立周期运行 |
| 覆盖 | 已知失败路径 | 任意来源的最终不一致 |
| 依赖 | 依赖链路本身正常 | 不依赖链路,直接比对终态 |
补偿失败、消息丢失、程序缺陷造成的漂移,只有对账能发现并修复。补偿处理“已知的失败”,对账兜住“未知的漂移”——后者是前者的最后一道防线。
补偿链与对账逻辑天然跨服务,而服务边界即团队边界。由此派生的都不是纯技术问题:
这是“系统越分布,事务越业务化”的组织投影:一致性的 C 从数据库内的约束,变成跨团队协作的契约。