各数据库MVCC(多版本并发控制的比较)
各数据库MVCC(多版本并发控制的比较)
前言
在并发操作中,当正在写时,如果有用户在读,这时写可能只写了一半,如一行的前半部分刚写入,后半部分还没有写入,这时可能读的用户读取到的数据行的前半部分数据是新的,后半部分数据是原来的,这就导致了数据一致性问题
。解决这个问题的最简单的方法是使用读写锁,写的时候不允许读,正在读的时候也不允许写,但这种方法会导致读和写的操作不能并发执行。于是,有人想到了一种能够让读写并发执行的方法,这种方法就是MVCC。
实现MVCC的方法有以下两种。
- 第一种:写新数据时,把原数据移到一个单独的位置,如回滚段中,其他用户读数据时,从回滚段中把原数据读出来。
- 第二种:写新数据时,原数据不删除,而是把新数据插入进来。
实现原理
Oracle、MySQL、PostgreSQL、OceanBase 四种数据库在 MVCC(多版本并发控制) 上的实现原理和差异。
1. Oracle
- 存储机制:
- Oracle 并不显式存储多版本,而是依赖 Undo Segment(撤销段)。
- 实现方式:
- 数据块只保存最新版本的数据。
- 旧版本数据通过 Undo log(回滚段)来恢复。如果一个查询需要访问旧版本,就根据回滚信息重构“过去的视图”。
- 隔离级别:
- 默认 Read Committed(读已提交),支持 Serializable(通过一致性读模拟)。
- 特点:
- 高度依赖回滚段和一致性读逻辑。
- 查询时重构数据,不会显式保留多份版本。
- 一致性读开销较大,但保证事务隔离性强。
2. MySQL (InnoDB)
- 存储机制:
- 在数据行里维护版本控制字段。
- 隐藏列:如 DB_TRX_ID(最后修改该行的事务ID),DB_ROLL_PTR(指向 Undo Log 的指针),DB_ROW_ID(隐藏主键)。
- 实现方式:
- 每次事务更新行时,先写 Undo Log,指针挂在记录上。
- 查询时通过事务 ID 和 Undo Log 找到可见的版本。
- 隔离级别:
- 默认 Repeatable Read(可重复读),通过 MVCC 和间隙锁(next-key lock)避免幻读。
- Read Committed 也能实现,但需要额外快照判断。
- 特点:
- MVCC 基于 Undo Log 链表。
- 快照版本检查规则:只能看到在事务开始前已提交的版本,以及自己事务修改的版本。
3. PostgreSQL
- 存储机制:
- 采用 多版本存储,行版本直接存储在数据表中,而不是依赖 Undo Log。
- 每行有 xmin(插入事务ID)、xmax(删除/更新事务ID)。
- 更新时不是覆盖,而是 生成新版本的行,旧行标记为“过期”。
- 实现方式:
- 查询时根据事务快照判断哪些版本可见。
- 旧版本数据不会立刻清理,需要 VACUUM 回收。
- 隔离级别:
- 默认 Read Committed。
- 支持 Repeatable Read 和 Serializable,实现基于快照隔离(Snapshot Isolation)。
- 特点:
- 真正的多版本存储:同一行在物理表里有多个版本。
- 依赖 VACUUM 清理过期版本,否则膨胀严重。
- 读取无需回滚段,直接在表里判断可见性,查询性能稳定。
4. OceanBase
- 存储机制:
- 参考 Oracle 设计,但实现更接近 混合型 MVCC。
- 数据行保留最新版本。
- 历史版本通过 多版本数据链(MemTable + SSTable 中的多版本) 维护。
- 实现方式:
- 写时复制(Copy-on-Write):写入新版本,同时历史版本保留在多版本链表中。
- 查询时根据事务快照在多版本链中查找可见版本。
- 隔离级别:
- 默认 Snapshot Isolation(快照隔离,类似 Oracle 的读已提交+一致性读)。
- 支持 Serializable。
- 特点:
- 保持强一致性(分布式事务协议 Paxos + MVCC)。
- 和 PostgreSQL 一样保留多版本,但更偏向 Oracle 的 undo/redo 结合机制。
在分布式场景下做了优化,减少回溯开销。
对比
| 数据库 | 版本标识方式 | 旧版本存储位置 | 可见性判断规则 | 默认隔离级别 | 特点与限制 |
|---|---|---|---|---|---|
| Oracle | 系统变更号 SCN | 回滚段(Undo Segment) | 1. 读已提交:每条语句获取最新 SCN,仅看到该 SCN 之前已提交的数据 2. 可串行化:事务启动时获取 SCN,整个事务复用该 SCN |
读已提交(Read Committed) | 1. 回滚段空间独立,不会膨胀主表 2. 高并发时回滚段可能成为热点,大事务回滚会长时间锁表 |
| MySQL (InnoDB) | 事务 ID + 回滚指针 | 回滚段(Undo Log) | 1. 可重复读:事务首次快照读时生成 ReadView,后续复用 2. 读已提交:每条快照读重新生成 ReadView |
可重复读(Repeatable Read) | 1. 回滚段位于主表空间,长事务可能导致空间膨胀 2. 通过 Next-Key Lock 在 RR 级缓解幻读 |
| PostgreSQL | 事务 ID (XID) | 主表页内保留旧元组 | 1. 快照读:事务启动时获取快照(活跃 XID 列表),仅看到 xmin ≤ 当前 XID 且 xmax 不可见的元组 2. 通过 Vacuum 定期清理死元组 |
读已提交(Read Committed) | 1. 回滚快,无回滚段竞争 2. 表膨胀明显,依赖 Vacuum 回收空间,长事务会阻塞清理 |
| OceanBase | 全局时间戳 GTS | 行操作链 + 内存 B 树索引 | 1. 读已提交:每条语句取新 GTS 2. 可重复读:事务启动时取 GTS,全程复用 3. 可串行化:在 RR 基础上加行锁 |
读已提交(Read Committed) | 1. 集中式 GTS 高吞吐(200 万 TPS) 2. 分布式两阶段提交保证多分区一致性 3. 多租户场景下同样有效 |

👉 总结:
- Oracle / MySQL 用 Undo Log 间接实现多版本,存储节省但读旧版本需要回溯。
- PostgreSQL 是最纯粹的 MVCC,每次更新都生成新版本,查询直接过滤版本,可读性好,但需要 VACUUM 清理。
- OceanBase 结合了 Oracle 和 PG 思路,在分布式环境下用 MVCC + Paxos 提供全局一致性。

浙公网安备 33010602011771号