MySQL--MVCC
1. 什么是 MVCC?
MVCC (Multi-Version Concurrency Control,多版本并发控制)
核心思想:通过保存数据的多个版本,让读操作无需阻塞写操作,从而实现 高并发下的读写一致性。
MVCC 主要用来实现:
一致性读(Consistent Read,快照读)
非阻塞读(读不加锁)
👉 MVCC 是 InnoDB 实现 事务隔离性(Isolation) 的关键机制。
2. MVCC 依赖的机制
(1) 隐藏字段
InnoDB 在每行数据后面都会存储一些隐藏字段:
DB_TRX_ID:最近修改该行的事务 ID
DB_ROLL_PTR:回滚指针,指向 Undo Log(用于找到之前版本的数据)
DB_ROW_ID:自增 ID(如果没有主键时生成)
(2) Undo Log
作用:保存数据的旧版本,用于回滚 & MVCC 提供历史版本。
每次
UPDATE/DELETE
都会生成对应的 Undo Log 记录旧值。链表结构:通过 DB_ROLL_PTR 串联起来,形成一条数据的版本链。
(3) Read View
Read View = 一致性视图,用于判断当前事务能看到哪些版本。
在事务执行 快照读 时生成,包含以下关键字段:
m_ids:系统当前活跃事务 ID 列表
min_trx_id:活跃事务的最小 ID
max_trx_id:下一个将分配的事务 ID(未分配,意味着比它大的事务还没开始)
creator_trx_id:创建该 Read View 的事务 ID
👉 Read View 决定了 可见性规则。
3. MVCC 的可见性规则
当事务执行 快照读 时,判断某个版本是否可见的逻辑:
设数据版本的 DB_TRX_ID = X:
如果 X < min_trx_id → 表示事务已经提交 → 可见
如果 X ≥ max_trx_id → 表示事务还没开始 → 不可见
如果 min_trx_id ≤ X < max_trx_id → 表示事务可能在活跃中:
如果 X ∈ m_ids(活跃事务列表) → 不可见(因为还没提交)
如果 X ∉ m_ids → 可见(说明已经提交了)
4. 快照读 vs 当前读
快照读(Snapshot Read)
SELECT * FROM table WHERE ...;
不加锁,读取 Read View 可见的版本
使用 MVCC 保证一致性
当前读(Current Read)
SELECT ... FOR UPDATE
/SELECT ... LOCK IN SHARE MODE
UPDATE
/DELETE
/INSERT
读取最新版本,并且对数据加锁,防止并发修改
👉 总结:快照读 → MVCC 机制; 当前读 → 加锁机制。
5. MVCC 与事务隔离级别
读未提交(Read Uncommitted)
不使用 MVCC,直接读最新版本,可能产生脏读。
读已提交(Read Committed)
每次执行
SELECT
都生成新的 Read View。(和可重复读不同的关键点)只能看到已提交事务的数据,避免脏读。
但会产生 不可重复读。
可重复读(Repeatable Read,InnoDB 默认)
在事务第一次
SELECT
时生成 Read View,整个事务期间都使用这个快照。(和读已提交不同的关键点)避免脏读、不可重复读。
但会有 幻读 → 通过 间隙锁(Gap Lock) 解决。
串行化(Serializable)
不用 MVCC,所有读写都加锁,效率最低。
6. 一个更新操作下的 MVCC 示例
假设有一行数据:
id=1, name="Tom"
T1:事务 A 更新
name="Jerry"
在 Undo Log 中保存旧版本
"Tom"
DB_TRX_ID = A
T2:事务 B 开始,生成 Read View(活跃事务包含 A)
B 执行
SELECT name FROM user WHERE id=1;
判断可见性:因为 A 还没提交,所以 B 只能看到
"Tom"
T3:事务 A 提交
T4:事务 B 再次执行
SELECT
由于在 RR 隔离级别 下,B 使用的还是最初的 Read View
所以仍然看到
"Tom"
(保证可重复读)👉 这就是 MVCC 保证一致性读 的效果。
7. 为什么需要 MVCC?
如果没有 MVCC
所有读操作都必须加锁 → 读写互斥 → 并发性能极差。
有了 MVCC
读操作可以不加锁,直接通过历史版本判断可见性;
读写分离,提升数据库并发性能。
✅ 一句话总结:
MySQL InnoDB 的 MVCC 通过 隐藏字段 + Undo Log + Read View 实现,让事务在并发下能“读旧版本”,从而做到 非阻塞读 和 一致性读,是高性能的关键。