Skip to content

MVCC 多版本并发控制

什么是 MVCC

MVCC(Multi-Version Concurrency Control)是一种并发控制机制,核心思想是:同一个数据的多个版本并存,读操作不阻塞写,写操作不阻塞读

PostgreSQL 的 MVCC 实现

两个关键字段

每行数据都有两个隐藏列:

sql
xmin  -- 创建这条数据的事务ID(谁创建的)
xmax  -- 删除/更新这条数据的事务ID(谁要删除/更新它)
sql
-- PostgreSQL 可以查看隐藏列
SELECT xmin, xmax, * FROM users;

事务快照

每个事务开始时获取一个"快照",快照决定你能看到哪些版本的数据:

sql
-- 事务隔离级别决定快照内容
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;

快照包含三个关键信息:

  • xmin - 正在运行的最早事务ID
  • xmax - 下一个将被分配的事务ID
  • xip_list - 正在运行的事务ID列表

可见性判断规则

一条数据对当前事务可见,当且仅当:
1. 该数据的 xmin 事务已经提交(在快照的 xip_list 之外且已提交)
2. 该数据的 xmax = 0(没有被删除),或者 xmax 事务未提交
3. 当前事务自己创建的数据始终可见

具体例子

sql
-- 时间线:
-- T1: 事务100 插入一行 (id=1, name='A')
-- T2: 事务101 修改同一行 (name='B')  
-- T3: 事务102 查询,看到的是 'A' 还是 'B'?

-- 取决于事务102的隔离级别和快照时机

更新操作的本质

sql
UPDATE users SET name = 'B' WHERE id = 1;

-- 不是原地修改,而是:
-- 1. 将原行的 xmax 设为当前事务ID(原行标记为"已删除")
-- 2. 插入新行,xmin = 当前事务ID(新行标记为"已创建")

VACUUM 的作用

死亡元组(xmax 已提交但还未被清理的行)会越来越多:

sql
-- 手动清理
VACUUM users;

-- 查看死亡元组数量
SELECT n_dead_tup FROM pg_stat_user_tables WHERE tablename = 'users';

这就是为什么 PostgreSQL 需要定期 VACUUM,否则表会越来越膨胀。

MVCC 的优缺点

优点

  • 读操作不阻塞写,写操作不阻塞读(大部分场景)
  • 不用长时间加读锁,适合高并发

缺点

  • 同一份数据可能有多个版本,占用更多空间
  • 复杂的可见性判断,增加 CPU 开销
  • UPDATE 会产生死亡元组,需要 VACUUM 清理

不同数据库的 MVCC

MySQL (InnoDB)

  • 使用 roll_pointer 指向 undo log 中的旧版本
  • 通过 ReadView 判断可见性
  • 聚簇索引直接存储数据

PostgreSQL

  • 直接在表文件中存储多版本
  • 用 xmin/xmax 标记版本
  • 依赖 VACUUM 清理死亡元组

Oracle

  • 使用 undo segment 存储旧版本
  • 通过 SCN(系统变更号)判断版本
  • 自动清理,不需要手动 VACUUM

对比总结

特性PostgreSQLMySQL (InnoDB)Oracle
版本存储位置表文件undo logundo segment
版本标识xmin/xmaxroll_pointerSCN
清理机制VACUUMpurge线程自动清理
索引结构Heap Only聚簇索引堆表+索引

基于 MIT 许可发布