ACID 与隔离级别
ACID 四个字母里,隔离性(Isolation)是最贵的一个。本章讲四性的精确含义、并发事务的经典异常、四个隔离级别与快照隔离,以及"快照隔离不等于可串行化"这个最重要的坑(→ 并发控制)。
ACID 的精确含义
Section titled “ACID 的精确含义”- 原子性(Atomicity):事务要么全做、要么全不做——由 undo 日志实现(→ 日志与恢复);
- 一致性(Consistency):事务把数据库从一个一致状态带到另一个——由约束与应用语义保证;
- 隔离性(Isolation):并发事务互不干扰——由并发控制实现,本章主角;
- 持久性(Durability):提交后崩溃也不丢——由 redo 日志与 fsync 实现(→ 日志与恢复)。
"一致性"在 ACID 与 CAP 中含义不同(→ 故障模型与 CAP):ACID 的 C 是业务约束的一致(约束满足),CAP 的 C 是线性一致性(多副本像单机)——同词不同义,讨论时先定义。
两个并发事务(读写交错)的经典异常:
| 异常 | 定义 | 例子 |
|---|---|---|
| 脏读 | 读到未提交的数据 | T2 读到 T1 写入但随后回滚的值 |
| 不可重复读 | 两次读同一行值不同 | T2 在两次读之间提交了更新 |
| 幻读 | 两次范围查询行集不同 | T2 插入了新行,出现在 T1 的第二次查询 |
| 写偏斜(write skew) | 各自写不冲突的行,合起来破坏约束 | 值班医生互相请假:都读"对方在值班",都写自己请假 |
脏读、不可重复读、幻读在快照隔离下都消失;写偏斜是快照隔离独有的残留(见下)。
四个隔离级别
Section titled “四个隔离级别”| 级别 | 脏读 | 不可重复读 | 幻读 | 实现 |
|---|---|---|---|---|
| 读未提交(RU) | 可能 | 可能 | 可能 | 不加读锁 |
| 读已提交(RC) | 不会 | 可能 | 可能 | 读已提交版本 |
| 可重复读(RR) | 不会 | 不会 | 可能* | 快照(事务首读) |
| 可串行化 | 不会 | 不会 | 不会 | 锁或 SSI |
* MySQL InnoDB 的 RR 用间隙锁把幻读也防了;PostgreSQL 的 RR 就是快照隔离(有写偏斜)。
默认隔离级别因数据库而异:Oracle/PostgreSQL 默认 RC,MySQL InnoDB 默认 RR,Postgres 的 SERIALIZABLE 是 SSI——跨数据库时别假设默认级别相同。
快照隔离与写偏斜
Section titled “快照隔离与写偏斜”快照隔离(snapshot isolation):每个事务看到一致的历史快照(MVCC,→ 并发控制),写入时若与并发提交冲突则回滚(first-updater-wins)。读永远不加锁,性能好。
快照隔离不等于可串行化:写偏斜穿透它。值班医生的例子:两个事务读到的快照都是"对方在值班",各自写自己(写的行不同,不冲突),结果两人都走了——约束被破坏,但每个事务单独看都正确。写偏斜 = 读旧快照做出的决定 + 写入不同的行。
根治是可串行化:
- 两阶段锁(S2PL):读加锁、写加锁到提交——性能差;
- SSI(可串行化快照隔离):PostgreSQL 的做法——快照隔离 + 检测"危险结构"(读写依赖成环),发现即回滚一个——性能比锁好得多。
业务上意味着什么
Section titled “业务上意味着什么”大多数数据库默认级别并非可串行化——这意味着:
- 默认级别下,写偏斜类 bug 是你的业务逻辑要自己防的(约束在应用层检查、或显式加锁/升级级别);
- REPEATABLE READ 的名字有误导:它防的是"同一行两次读不同",不防写偏斜与(某些实现的)幻读;
- 高正确性要求(余额、库存、值班表)要显式选择:可串行化级别、或
SELECT ... FOR UPDATE显式锁。
- 原子性靠 undo 日志、持久性靠 redo 日志;隔离性是最贵的一性
- 异常四件套:脏读、不可重复读、幻读、写偏斜;前三个在快照隔离下消失
- 快照隔离不等于可串行化(写偏斜):读旧快照 + 写不同的行
- 默认隔离级别因数据库而异;"一致性"在 ACID 与 CAP 中同词不同义
- 高正确性要求显式选择:SERIALIZABLE / SSI / FOR UPDATE