跳转到内容

Gray 1978 — 数据库操作系统讲义,事务/2PL/2PC/恢复一次讲完

是什么

Gray 1978 是 Jim Gray 在 1977 年慕尼黑暑期学校讲的一份130 页讲义。日常类比:像一位主厨把”开餐厅每天要做的所有事”——进货、备料、点菜、上菜、对账、关店——一次性写成厨房手册。后来所有数据库内核(PostgreSQL / MySQL / Oracle / SQL Server / Spanner)的厨房,都是照这本手册改的。

它不是一篇论文,是操作系统暑期学校的讲义,1978 年收入 Springer LNCS 60。题目用的是”Data Base Operating Systems”——把数据库当成”建在普通操作系统之上的另一层操作系统”来讲。

讲义里第一次完整地把四件事讲在一起

  1. 事务(transaction)的语义 — BEGIN / COMMIT / ABORT
  2. 并发控制 — 两阶段封锁(2PL)+ 锁粒度层级
  3. 故障恢复 — 写前日志(WAL)+ DO/UNDO/REDO
  4. 分布式提交 — 两阶段提交(2PC)

今天数据库面试讲到的 80% 内核知识,源头就这一份。

为什么重要

不理解 Gray 1978,下面这些事都没法解释:

  • 为什么 PostgreSQL / MySQL 的事务接口叫 BEGIN ... COMMIT ... ROLLBACK —— 这三个动词是 Gray 1978 写下来的
  • 为什么所有 DBMS 默认要写 redo/undo 日志,宁可慢也不肯关 —— 因为 Gray 论证了”没有日志的崩溃恢复必然丢数据”
  • 为什么 spanner / cockroachdb 跨大洲提交事务,对外仍然是一句 COMMIT —— Gray 把 2PC 设计成”用户看不见的内部协议”
  • 为什么微服务时代冒出 Saga / TCC 模式 —— 因为 Gray 自己就指出 2PC 在协调者挂掉时会阻塞,长事务下不可接受
  • 为什么 aries-1992bernstein-1981-ccgray-1981-transaction 都把 1978 这份讲义当起点引

核心要点

1. 事务 = 原子单位

事务是一段代码,对外承诺要么全做、要么一件没做。例子(Gray 原文用的):

BEGIN
account_A := account_A - 100
account_B := account_B + 100
COMMIT

中间任何一步出错或断电,最终账面要么是”两个账户都没动”,要么是”A 减 100、B 加 100”。绝不允许 A 减了但 B 没加。这一条性质叫原子性

Gray 同时指出事务还要保证:

  • 持久性(durability):COMMIT 一旦返回,数据写到磁盘后断电也不丢
  • 一致性(consistency):事务结束后数据库符合所有约束(如总余额不变)
  • 隔离性(isolation):多个事务并发跑,看起来像一个一个跑

四个性质合起来,五年后 Härder-Reuter 1983 给它起了名字叫 ACID。1978 年原文还没有这个缩写,但语义已经全在

2. 两阶段封锁(2PL)= 并发协议

并发问题:两个事务同时读写同一行,结果可能错。Gray 给的协议:

  • 增长阶段(growing phase):事务一直加锁,永不释放
  • 收缩阶段(shrinking phase):开始放锁后,永不再加新锁

定理:所有事务都遵守 2PL → 执行结果等价于某个串行顺序(叫”可串行化”)。

日常类比:像图书馆借书规则。你可以一次借 5 本(增长),但开始还书后就不能再借(收缩)。这条规则保证了”借书人之间的顺序”始终是合理的。

3. 锁粒度层级 + 意向锁

不是所有锁都加在”一行”上。Gray 列了五层粒度:

database → area → file → record → field

粗粒度(整库锁)省管理开销但并发差;细粒度(行锁)并发好但锁数量爆炸

Gray 发明了**意向锁(intention locks)**让粗细粒度共存:

  • IS(intention shared):我准备在子节点加 S 锁
  • IX(intention exclusive):我准备在子节点加 X 锁
  • SIX:S + IX 组合

类比:你要进图书馆某个房间翻书,先在大门口贴张”我要进 305 房间翻书”的牌子(意向锁)。别人想锁整层楼时一看牌子就知道有人在用,避免冲突。

PostgreSQL / MySQL / Oracle 今天的锁管理器都是这套表格的工程实现。

4. WAL + DO/UNDO/REDO = 恢复模型

崩溃恢复的核心问题:磁盘只写了一半事务的页就断电了,重启后怎么办?

Gray 给的答案——写前日志(WAL,write-ahead logging)

  1. 修改数据页之前,把”我打算改什么”写到日志(顺序追加,便宜)
  2. 真正改数据页(可能延迟)
  3. COMMIT 时只要日志落盘就算事务完成

崩溃后扫描日志做三类操作:

  • DO:第一次执行
  • REDO:日志里说改过、但磁盘上看不到 → 重做
  • UNDO:日志里说改过、但事务没 COMMIT → 撤销

这三个动词后来成了 aries-1992 算法的骨架。

5. 两阶段提交(2PC)= 分布式原子

跨多台机器的事务,每台机器必须要么都 COMMIT、要么都 ABORT。Gray 的协议:

  • 阶段 1(prepare):协调者问每个节点”你能提交吗?“,节点把 redo 日志写好后回 YES
  • 阶段 2(commit):所有节点都 YES → 协调者发 COMMIT;任何一个 NO → 发 ABORT

类比:婚礼上牧师问双方”你愿意吗?“两边都说”我愿意”才宣布婚礼成立。

已知缺陷(Gray 自己写在原文里):协调者在阶段 2 之前挂掉,所有节点都阻塞,必须等协调者回来。这就是后来 3PC、Paxos commit、Saga 模式要解决的问题。

实践案例

案例 1:PostgreSQL 的 BEGIN ... COMMIT 直接来自 1978

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 'A';
UPDATE accounts SET balance = balance + 100 WHERE id = 'B';
COMMIT;

三个动词、原子语义、隔离级别 —— 每一项都是 Gray 1978 定义的。

案例 2:MySQL InnoDB 的锁层级

InnoDB 锁的类型:IS / IX / S / X / SIX —— 直接照搬 Gray 的意向锁表格,连命名都没改。

案例 3:Spanner 跨洲事务用 2PC

Spanner 跨数据中心写一笔订单,内部跑 Paxos + 2PC。对外仍然是一句 COMMIT。Gray 1978 的协议,46 年后跑在跨洲光纤上。

踩过的坑

  1. Gray 1978 ≠ Gray 1981:1978 是 130 页讲义(全景课),1981 是 VLDB 短论文(把”事务”升华为通用抽象)。引用时分清两份
  2. ACID 不在原文:缩写是 Härder-Reuter 1983 起的,1978 只有四个性质的口语描述
  3. 2PL 不防死锁:两阶段封锁保证可串行化,但两个事务互锁仍然会死锁,要靠死锁检测器或超时
  4. 2PC 阻塞问题:协调者挂掉所有参与者卡住——这是 2PC 的根本缺陷,不是实现 bug。Saga / Paxos commit 才真正解掉

适用 vs 不适用场景

适用

  • 关系型数据库内核(OLTP)
  • 单机或局域网内的强一致事务
  • 需要”全做或不做”语义的关键业务(金融、订单、库存)

不适用

  • 长事务(人审批一周)→ 协调者锁太久,用 Saga 拆成补偿动作
  • 跨广域网高延迟 → 2PC 阻塞窗口太大,用 Paxos commit 或最终一致
  • 海量只读分析(OLAP)→ 完整 ACID 太重,用 snapshot / MVCC

历史小故事(可跳过)

  • 1976:IBM System R 已把 2PL、锁粒度、WAL 跑在原型机上,Gray 是核心成员
  • 1977-78:Bayer 在慕尼黑办暑期学校,Gray 讲数据库专题,整理成 130 页讲义
  • 1981 / 1992:Gray VLDB 把”事务”升华成通用抽象;Mohan 工程化为 aries-1992

学到什么

  1. 事务是数据库的”核心数据类型”——并发/恢复/分布式都围绕它
  2. 三条主线:并发=2PL,恢复=WAL+DO/UNDO/REDO,分布式=2PC
  3. 2PC 阻塞:原子性 vs 可用性的张力 46 年没变;教学讲义反成开山之作

延伸阅读

关联

反向链接

  • aries-1992 —— ARIES 1992 — 数据库崩溃后怎么把账目对回来
  • bayou-1995 —— Bayou — 离线先改本地,再回来和别人合并
  • bernstein-1981-cc —— Bernstein 1981 并发控制综述 — 把分布式数据库的 20+ 算法整成两条主线
  • cockroachdb —— CockroachDB — 分布式 SQL 数据库
  • dewitt-gray-1992 —— DeWitt-Gray 1992 — 并行数据库取代专用机的宣言
  • gray-1981-transaction —— Gray 1981 — 把”事务”提升为通用抽象
  • multics-1965 —— MULTICS 1965 — 把计算机做成像电力一样的公共服务
  • postgresql —— PostgreSQL — 工业级关系数据库
  • presumed-abort-1986 —— Presumed Abort/Commit — 让 2PC 少写日志少发消息的两个默认共识
  • rabin-ot-1981 —— Rabin 遗忘传输 — 发送方永远不知道你收到了什么
  • saga-1987 —— Sagas — 长事务拆成一串能”反向走回去”的小事务
  • spanner —— Spanner — 全球分布式 SQL 数据库
  • unix-1974 —— UNIX 1974 — 用极小内核做出能用的分时系统