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- 正在运行的最早事务IDxmax- 下一个将被分配的事务IDxip_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
对比总结
| 特性 | PostgreSQL | MySQL (InnoDB) | Oracle |
|---|---|---|---|
| 版本存储位置 | 表文件 | undo log | undo segment |
| 版本标识 | xmin/xmax | roll_pointer | SCN |
| 清理机制 | VACUUM | purge线程 | 自动清理 |
| 索引结构 | Heap Only | 聚簇索引 | 堆表+索引 |