各数据库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 提供全局一致性。
posted @ 2025-09-22 17:13  数据库小白(专注)  阅读(33)  评论(0)    收藏  举报