MySQL 幻读详解
幻读(Phantom Read)是数据库并发事务中的一种现象,理解它需要先区分几个相关概念。
一、什么是幻读
幻读 :在一个事务内,两次执行相同的范围查询 ,第二次查询却看到了第一次查询没有看到的新插入的行(像出现了"幻觉")。
注意区分三个概念:
| 现象 | 定义 | 关注点 |
|---|---|---|
| 脏读 | 读到其他事务未提交的数据 | 未提交数据 |
| 不可重复读 | 两次读同一行,值变了 | 同一行的修改 |
| 幻读 | 两次范围查询,结果集行数变了 | 结果集的新增/删除 |
关键区别:不可重复读针对单行值的更新,幻读针对结果集行数的增减。
二、幻读是怎么出现的
场景演示
假设表 t 有字段 id(主键)、c(普通索引)、d,隔离级别为 可重复读(RR),但先看不加锁的情况:
text
时刻 事务A 事务B
T1 BEGIN;
T2 SELECT * FROM t WHERE id > 2; -- 返回 1 行(id=3)
T3 BEGIN;
T4 INSERT INTO t VALUES(5,...);
T5 COMMIT;
T6 SELECT * FROM t WHERE id > 2; -- 若返回 2 行(id=3,5) → 幻读
在 RR 隔离级别下,MySQL 通过 MVCC 快照读 让 T6 仍然只看到 1 行,从而避免了大部分幻读。但幻读在以下情况仍会出现:
1. 当前读(加锁读)导致的幻读
sql
-- 事务A
BEGIN;
SELECT * FROM t WHERE id > 2 FOR UPDATE; -- 当前读,加锁
-- 此时锁住了 id>2 的范围
-- 事务B
INSERT INTO t VALUES(5,...); -- 如果没被间隙锁挡住,就能插入
-- 事务A 再查
SELECT * FROM t WHERE id > 2 FOR UPDATE; -- 多出一行 → 幻读
当前读 (FOR UPDATE、LOCK IN SHARE MODE、UPDATE、DELETE)读取的是最新数据,不走 MVCC 快照,所以可能看到新插入的行。
2. 快照读与当前读混用导致的幻读
这是最经典的"幻读"案例:
text
时刻 事务A 事务B
T1 BEGIN;
T2 SELECT * FROM t WHERE id > 2; -- 快照读,返回 id=3
T3 BEGIN;
T4 INSERT id=5; COMMIT;
T5 UPDATE t SET d=100 WHERE id > 2; -- 当前读!会更新 id=3 和 id=5
T6 SELECT * FROM t WHERE id > 2; -- 返回 id=3,5 → 幻读
原因 :T5 的 UPDATE 是当前读,它更新了事务B新插入的 id=5 这行。而 T6 的快照读虽然用的是 T1 时刻的快照,但 UPDATE 相当于把这行"变成"了自己事务修改过的行,后续快照读就能看到它了(这行对本事务可见)。
这是 MySQL RR 级别下唯一无法完全避免的幻读场景。
三、InnoDB 如何解决幻读
1. MVCC(多版本并发控制)------ 解决快照读幻读
-
每行记录有隐藏字段
DB_TRX_ID(最近修改事务ID)、DB_ROLL_PTR(回滚指针) -
事务开始时生成 ReadView,记录当前活跃事务列表
-
快照读时,只读取"对本事务可见"的版本
-
因此事务A两次普通 SELECT 看到的是同一个快照,不会幻读
2. 间隙锁(Gap Lock)+ Next-Key Lock ------ 解决当前读幻读
-
Record Lock:锁单行记录
-
Gap Lock:锁一个范围区间(开区间),防止其他事务在间隙中插入
-
Next-Key Lock = Record Lock + Gap Lock,锁住记录及其前面的间隙
例如 SELECT * FROM t WHERE id > 2 FOR UPDATE,InnoDB 会对 (2, +∞) 加 Next-Key Lock,其他事务想插入 id=5 会被阻塞,从而防止当前读幻读。
注意:在 RC(读已提交) 隔离级别下,间隙锁基本失效(只锁行不锁间隙),所以 RC 无法防幻读。
四、总结
| 隔离级别 | 快照读幻读 | 当前读幻读 |
|---|---|---|
| 读未提交 RU | 会 | 会 |
| 读已提交 RC | 会 | 会 |
| 可重复读 RR | 不会(MVCC) | 不会(间隙锁)* |
| 串行化 Serializable | 不会 | 不会 |
* 例外:快照读 + 当前读混用时,RR 仍可能出现幻读,因为 UPDATE 会把新插入行纳入本事务可见范围。
核心要点:
-
幻读 = 范围查询结果集行数变化
-
快照读靠 MVCC 防幻读,当前读靠 间隙锁 防幻读
-
RR 下混用快照读和当前读,是幻读的唯一漏网之鱼
-
很多公司用 RC + 行锁,此时需在应用层处理幻读问题