区分 快照读(Snapshot Read) 和 当前读(Current Read)
区分 快照读(Snapshot Read) 和 当前读(Current Read)
- 1. 关键概念区分
- (1) 快照读(Snapshot Read)
- (2) 当前读(Current Read)
- 2. 幻读发生的条件
- 关键分析:
- 3. InnoDB 如何解决幻读?
- 4. 总结:幻读的本质
1. 关键概念区分
(1) 快照读(Snapshot Read)
- 操作:普通
SELECT语句(不加锁)。 - 原理:基于事务开始时创建的 一致性读视图(Consistent Read View),读取数据的历史版本。
- 效果:在 RR(可重复读) 隔离级别下:同一事务内多次快照读,结果完全一致(即使其他事务提交了新数据)。✅ 天然免疫幻读(因为看不到新插入的数据)。
(2) 当前读(Current Read)
- 操作:加锁的读操作(如
SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE)或写操作(UPDATE/DELETE/INSERT)。 - 原理:读取最新提交的数据,并加锁保护数据一致性。
- 风险:在 RR(可重复读) 隔离级别下:可能触发幻读(需依赖 Next-Key Lock 防幻读)。
2. 幻读发生的条件
幻读仅发生在当前读的场景中!以下是经典幻读流程:
| 时间 | 事务A(RR隔离级别) | 事务B(已提交) |
|---|---|---|
| t1 | SELECT * FROM t WHERE age=3; (快照读,返回空) | |
| t2 | INSERT INTO t (id, age) VALUES (10, 3); 提交 | |
| t3 | SELECT * FROM t WHERE age=3; (快照读,仍返回空) | |
| t4 | UPDATE t SET name='test' WHERE age=3; (当前读,发现事务B插入的 age=3的记录) | |
| t5 | SELECT * FROM t WHERE age=3; (快照读,仍返回空) | |
| t6 | SELECT * FROM t WHERE age=3 FOR UPDATE; (当前读,返回 id=10的新记录) → 幻读! |
关键分析:
- t1 & t3:快照读始终看不到新数据(无幻读)。
- t4:
UPDATE是当前读,会看到事务B提交的新数据(age=3, id=10),并尝试更新它(此时事务A感知到新数据)。 - t6:加锁读(
FOR UPDATE)是当前读,直接看到新数据 → 事务A发现“凭空出现”的记录 → 幻读发生。
⚠️ 重点:事务A的 快照读始终一致,但 当前读会破坏一致性!
3. InnoDB 如何解决幻读?
在 RR(可重复读) 隔离级别下,InnoDB 通过 Next-Key Lock(临键锁) 防止当前读的幻读:
- 当事务A执行
SELECT ... FOR UPDATE WHERE age=3时:在age索引的(3,3)记录上加 S锁(记录锁)。在(3,3)和(7,7)之间的间隙加 GAP锁(间隙锁)。 - 效果:其他事务无法插入新的
age=3的记录(如(3,10))。事务A的当前读始终返回相同结果(无幻读)。
4. 总结:幻读的本质
| 操作类型 | 是否可能幻读 | 原因 |
|---|---|---|
| 快照读(普通SELECT) | ❌ 不可能 | MVCC 读取历史版本,无视新数据 |
| 当前读(加锁读/写操作) | ✅ 可能(不加锁时) | 读取最新数据,可能看到其他事务的插入 |
