Berenson 1995 — ANSI SQL 隔离级别的漏洞与快照隔离
是什么
这篇 1995 年微软研究报告是数据库界的**「皇帝新衣」揭穿文**:ANSI SQL 标准里定义的四个隔离级别(READ UNCOMMITTED / READ COMMITTED / REPEATABLE READ / SERIALIZABLE)在形式上很整齐,但 Berenson 等人证明——标准语义有洞、实现各说各话、现象命名混乱,按标准字面值实现并不能保证你以为的那几种异常真的不会发生。
日常类比:标准像一份安检清单——「要有三道门」。Berenson 指出:三道门可以都装了,但门之间的缝隙能让小偷照样钻过去;而且各家机场对「门」的定义还不一致,你以为过了安检,其实漏洞还在。
论文的另一大贡献:正式提出并分析 Snapshot Isolation(SI)——后来 PostgreSQL、Oracle、SQL Server 等「可重复读」的真正实现,往往更接近 SI 而非 ANSI 字面 SERIALIZABLE。
SI 的直觉:每个事务看到数据库在某一刻的「照片」;别人之后的写入不影响你已读的行,但两个事务若写同一行,后提交者失败。理解 SI 是读懂现代 MVCC 数据库行为的钥匙。
为什么重要
不理解这篇 critique,下面这些事说不清:
- 为什么 DBA 背了四个隔离级别名词,线上还是会出现写偏斜(write skew)、幻读——ANSI 定义没覆盖全
- 为什么 PostgreSQL 的
REPEATABLE READ行为跟 MySQL 不一样——各家实现映射到不同现象集合 - 为什么今天讨论 snapshot、MVCC、SSI(Serializable Snapshot Isolation)都要引用 Berenson——SI 概念出自此文
- 为什么「Serializable」在标准里既是隔离级别名又是理想语义——Berenson 把它拆成**异常现象(phenomena)**来谈更清楚
核心要点
论文核心贡献拆成 三件事:
-
批判 ANSI 定义:标准用「现象」描述隔离,但现象定义不完整、与实现脱节;按标准测试无法区分很多实际 bug。
-
现象分类清单:系统梳理脏读、不可重复读、幻读、写偏斜等,并说明在哪些级别理论上该禁止、实际上仍可能出现。
-
提出 Snapshot Isolation:事务读到的是启动时刻数据库的一致性快照;写冲突用「先提交者胜」检测。SI 禁止脏读/不可重复读,但不防止写偏斜——这是后来 SSI 要补的洞。
实践案例
案例 1:经典写偏斜(SI 拦不住)
-- 两人同时转岗:要求至少一名医生在岗-- Tx A -- Tx BSELECT count(*) FROM on_callWHERE role='doctor' AND on_duty=true;-- 都得到 1 -- 也得到 1UPDATE on_call SET on_duty=falseWHERE name='Alice'; UPDATE on_call SET on_duty=false WHERE name='Bob';COMMIT; COMMIT;-- 结果:0 人在岗!SI 允许,Serializable 应禁止Berenson 用这类例子说明:「可重复读」名字好听,不等于所有异常都消失。
案例 2:PostgreSQL 隔离级别对照
| PG 级别 | 接近语义 | 写偏斜 | 幻读(Fuzzy) |
|---|---|---|---|
| READ COMMITTED | 语句级快照 | 可能 | 可能 |
| REPEATABLE READ | Snapshot Isolation | 可能 | SI 下已处理很多幻读 |
| SERIALIZABLE | SSI(2000s+ 改进) | 禁止 | 禁止 |
读 PG 文档时要记得:RR ≈ Berenson 的 SI,不是 ANSI 纸面 SERIALIZABLE。
案例 3:用现象问实现方,而不是背级别名
问 DBA 五个问题:1. 会不会脏读?2. 同一事务内两次读同一行会不会变?3. 范围查询两次结果行数会不会变?4. 两个事务交叉更新不同行,会不会破坏全局不变式?(写偏斜)5. 全序等价于串行执行吗?(真 Serializable)Berenson 教的是用现象验收,而不是背 READ COMMITTED 四个单词。
案例 4:库存扣减的脏读(READ UNCOMMITTED 才允许)
-- Tx A: 扣库存但未提交 -- Tx B: 读库存做展示UPDATE stock SET qty = qty - 1 WHERE sku='X'; -- 仍 COMMIT 前 SELECT qty FROM stock WHERE sku='X'; -- 若隔离=RU,可能读到已减未提交的值绝大多数 OLTP 默认至少 READ COMMITTED,就是禁止这种脏读——但 Berenson 指出:光说级别名不够,要问「未提交数据会不会被别的事务看见」。
踩过的坑
-
把隔离级别当互斥档位:实际是「禁止哪些现象」的集合,集合边界实现-dependent。
-
以为 REPEATABLE READ = 可重复读一切:通常只保证已读行稳定,不保证谓词稳定(幻读/写偏斜仍可能)。
-
忽略 SI 与 Serializable 差距:PG 在 RR 下踩写偏斜坑的团队不少,直到改 SERIALIZABLE 或应用层加锁。
-
标准考试答案当生产真理:1995 年后工业界大量用 MVCC+SI,与 ANSI 字面不同步——要以厂商文档和现象测试为准。
-
在 SI 下用「锁表」思维:SI 靠快照与写冲突检测,滥用
SELECT FOR UPDATE可能不必要地降吞吐——先画现象再选锁。
适用 vs 不适用场景
适用:
- 设计金融、库存、排班等有全局不变式的事务逻辑
- 评测数据库隔离语义、写对比测试
- 读 PostgreSQL / Oracle 隔离文档前的必读背景
不适用:
- 纯只读分析查询、无并发写——隔离级别讨论价值低
- 最终一致性 NoSQL 场景——用不同理论框架(kafka-2011 等)
- 单线程 SQLite 默认——无并发则无隔离问题
- 应用层已用乐观锁/version 列兜底全部不变式——可简化隔离需求但仍需懂 SI 边界
历史小故事(可跳过)
- 1992:ANSI SQL 隔离级别进标准,业界以为事务并发有统一语言。
- 1995:Berenson TR 指出标准漏洞,提出 Snapshot Isolation。
- 2000s:PostgreSQL 等广泛实现 MVCC + SI。
- 2008+:Serializable Snapshot Isolation(SSI)在 PG 等补齐写偏斜,向真 Serializable 靠拢。
学到什么
- 隔离级别是现象集合,不是四个神圣档位;实现映射要实测。
- Snapshot Isolation 是工业界「可重复读」的真实面孔,但有写偏斜洞。
- 设计并发系统要问「哪种异常不能接受」,而不是「我上第几级」。
- 这篇 critique 的精神——质疑标准字面——在分布式事务(spanner-2012)时代同样适用。
- Snapshot Isolation 不是银弹:懂 SI 能解释 80% MVCC 库的行为,剩下 20% 写偏斜要靠 SSI 或应用锁。
延伸阅读
- 论文 PDF:Microsoft TR-95-51
- snapshot —— MVCC 快照机制工程实现
- PostgreSQL 文档:Transaction Isolation
- kafka-2011 —— 另一端的「放弃跨分区事务」思路
- raft —— 复制一致性;与单机隔离互补
关联
- snapshot —— SI 的工程载体,MVCC 读快照
- postgresql —— RR 实现贴近 SI 的代表
- mysql —— 隔离实现与 PG 差异的对照
- kafka-2011 —— 分布式日志放弃传统隔离的另一极
- spanner-2012 —— 全球分布下 Serializable 的工程尝试
反向链接
- kafka-2011 —— Kafka NetDB 2011 — 把消息中间件砍成”会写文件的水管”
- lipp-meltdown-2018 —— Meltdown — 乱序执行偷读内核内存
- mysql —— MySQL — 全球最流行关系数据库
- postgresql —— PostgreSQL — 工业级关系数据库
- raft —— Raft — 易理解的共识算法
- snapshot —— Snapshot — DAO 不花 Gas 也能投票的链下治理前端
- spanner-2012 —— Spanner 2012 — 用原子钟和 GPS 给全球数据库发时间戳