幻读(Phantom Read) 是指在一个事务中,两次相同的范围查询返回了不同数量 的行,主要由于其他事务插入新行导致。
产生幻读的核心原因:
1. 快照读 vs 当前读的混合使用
sql
-- 可重复读下,普通SELECT是快照读,基于MVCC版本链
SELECT * FROM users WHERE age > 20; -- 快照读,使用事务开始时的快照
-- 但某些操作会触发当前读
UPDATE users SET status = 1 WHERE age > 20; -- 当前读,看到最新提交的数据
SELECT * FROM users WHERE age > 20 FOR UPDATE; -- 当前读,加锁
2. MVCC的局限性
-
MVCC保证已存在的行读取一致性
-
但无法阻止其他事务插入新的行
-
事务开始时创建ReadView,只记录当时已存在的行版本
3. 锁机制的缺失
在标准SQL规范中,可重复读级别:
-
只对已存在的行加锁(行锁)
-
不对不存在的行(间隙)加锁
-
因此其他事务可以插入满足条件的新行
实际示例:
sql
-- 事务A
START TRANSACTION;
-- 第一次查询:返回id为1,2,3的3条记录
SELECT * FROM users WHERE id BETWEEN 1 AND 5;
-- 此时事务B插入id=4的新记录并提交
INSERT INTO users(id, name) VALUES (4, 'new_user');
COMMIT;
-- 事务A再次查询(可重复读应返回相同3条记录)
SELECT * FROM users WHERE id BETWEEN 1 AND 5; -- 仍然只看到id 1,2,3
-- 但事务A执行更新操作时
UPDATE users SET status = 1 WHERE id BETWEEN 1 AND 5;
-- 更新会作用到id=4的行(因为更新是当前读)
-- 然后事务A再次查询,就会看到4条记录 ← 这就是幻读
MySQL InnoDB的特殊处理
MySQL通过Next-Key Locking机制在可重复读级别避免了幻读:
sql
SELECT * FROM users WHERE age > 20 FOR UPDATE;
-- InnoDB会锁住age>20的整个范围(间隙锁+行锁)
-- 其他事务无法插入age>20的新行
总结表格:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交 | ✓ | ✓ | ✓ |
| 读已提交 | ✗ | ✓ | ✓ |
| 可重复读 | ✗ | ✗ | 可能发生 |
| 串行化 | ✗ | ✗ | ✗ |
关键点:
-
幻读专指新插入的行
-
可重复读能防止已存在行的修改,但不能防止新行的插入
-
实际应用中可通过SELECT ... FOR UPDATE加间隙锁来避免