一、MVCC 解决了什么问题?
🌱 背景:并发读写冲突
当多个事务同时操作同一行时,最经典的冲突是:
-
A 在读;
-
B 在写;
-
A 还没提交,B 改了数据;
-
如何让 A 看到一致的结果?
MVCC(Multi-Version Concurrency Control,多版本并发控制)
👉 解决的核心问题是:
✅ 在高并发环境下,读写不阻塞,并且保证事务隔离一致性(尤其是可重复读 / Repeatable Read)。
也就是说,MVCC 让:
-
“读操作”不用加锁就能读到事务一致视图;
-
“写操作”也能正常进行,不会阻塞读;
-
事务之间看到的数据是一致的(基于事务启动时的快照)。
二、MySQL 幻读解决了吗?
要看事务隔离级别和读类型:
读类型 | 读已提交 (RC) | 可重复读 (RR) | 说明 |
---|---|---|---|
普通快照读 (SELECT …) | ✅ 幻读存在 | ✅ 已解决 | RR 下 MVCC + 间隙锁配合消除幻读 |
当前读 (SELECT … FOR UPDATE / UPDATE / DELETE) | ✅ 幻读存在 | ✅ 已解决 | RR 下间隙锁和临键锁阻止插入 |
✅ 结论:
在 可重复读(RR) 隔离级别下,InnoDB 已经彻底解决了幻读问题。
三、MVCC 的实现原理(核心机制)
InnoDB 通过 隐藏列 + Undo Log + Read View 三个机制实现多版本并发控制。
1️⃣ 隐藏列(3个)
每行数据除了用户字段外,InnoDB 内部维护三个隐藏列:
-
DB_TRX_ID
:最后修改该行的事务 ID。 -
DB_ROLL_PTR
:指向 undo log 的指针(用于找到旧版本)。 -
DB_ROW_ID
:行的自增主键(非显式主键时存在)。
2️⃣ Undo Log(版本链)
每当事务修改一行数据时:
-
不直接覆盖,而是把旧值写入 undo log;
-
并在当前行的
DB_ROLL_PTR
指向该 undo log; -
从而形成一个“版本链”:
每个版本都有自己的事务 ID。
3️⃣ Read View(读视图)
当事务执行第一个一致性读时(例如 SELECT
),InnoDB 会生成一个 Read View,记录以下信息:
字段 | 含义 |
---|---|
m_ids |
当前系统中活跃事务的 ID 列表 |
min_trx_id |
当前活跃事务中最小的事务 ID |
max_trx_id |
当前还未分配的下一个事务 ID(上限) |
creator_trx_id |
当前事务自身 ID |
4️⃣ MVCC 读版本规则(判断可见性)
InnoDB 判断一行是否对当前事务可见,遵循如下规则:
设该行版本的 trx_id = X
条件 | 是否可见 |
---|---|
X < min_trx_id | ✅ 已提交的旧事务,数据可见 |
X ∈ m_ids | ❌ 正在执行的事务,不可见 |
X > max_trx_id | ❌ 新事务创建的数据,不可见 |
如果该版本不可见,就顺着 undo log 找上一个版本,直到找到可见版本。
👉 这就是“多版本”:读事务读的是自己快照中可见版本的链。
四、幻读问题 & 间隙锁 / 临键锁如何解决
1️⃣ 什么是幻读
幻读:事务 A 重复执行相同的查询,却发现多了(或少了)几行记录。
例:
2️⃣ MVCC 能防幻读吗?
👉 只对已存在的行有效;
但是对新插入的行(不存在的版本链)无能为力。
所以要用锁机制配合。
3️⃣ 间隙锁(Gap Lock)
作用于“记录之间的空隙”,阻止其他事务在间隙内插入数据。
例:
假设表中有 age=10, 20, 30
事务 A 会锁定:
→ 其他事务不能在这些区间插入新的行。
📌 解决了“幻读”的核心问题:
防止别的事务在当前事务扫描范围内插入新数据。
4️⃣ 临键锁(Next-Key Lock)
是 记录锁(Record Lock) + 间隙锁(Gap Lock) 的组合。
锁定“当前行 + 前一个间隙”。
例如:
假设有行 (age=20)
,临键锁会锁定区间 (10,20]
。
📌 优势:
-
保证唯一性;
-
避免插入相同主键;
-
避免幻读(锁定扫描范围内的间隙)。
五、例子总结:快照读 vs 当前读
场景 | 使用方式 | 锁类型 | 是否受MVCC影响 | 是否防幻读 |
---|---|---|---|---|
快照读 | SELECT ... |
无锁 | ✅ 是 | ✅ RR 下已解决 |
当前读 | SELECT ... FOR UPDATE 、UPDATE 、DELETE |
临键锁 / 间隙锁 | ❌ 否 | ✅ 防止插入幻读 |
六、总结对比表
机制 | 解决问题 | 原理 | 是否加锁 | 是否防幻读 |
---|---|---|---|---|
MVCC | 读写并发、可重复读 | undo log + Read View | ❌ | ✅(快照读) |
间隙锁 | 防止插入幻读 | 锁定记录间隙 | ✅ | ✅ |
临键锁 | 防止幻读 + 保证唯一性 | 记录锁 + 间隙锁 | ✅ | ✅ |
✅ 一句话总结:
MVCC 让读写不互斥(靠版本链解决读一致性);
间隙锁 & 临键锁让写不产生幻读(靠锁定范围防插入)。