解决幻读
(理解掌握 快照读 和 当前读 ,InnoDB避免幻读的方式。。。)
目录
- 一、幻读
- 二、 InnoDB 是怎么 “尽量避免” 幻读的?
- 三、 为什么 “没完全解决”幻读?
- 四、怎么避免这些特殊幻读?
一、幻读
同一个事务中,由于期间其他并发事务修改这个结果集,多次相同的查询在不同时间得到不同的结果集,就是幻读。
二、 InnoDB 是怎么 “尽量避免” 幻读的?
方案 A:快照读(普通 SELECT)→ MVCC
- 事务开始后,第一次执行 SELECT 时,会生成一个 Read View。
- 之后所有的普通 SELECT,都用这个 Read View 去 undo log 里找 “事务开始时” 的数据版本。
- 所以,即使别的事务插入了新数据,你也查不到,避免了幻读。
方案 B:当前读(SELECT ... FOR UPDATE / UPDATE / DELETE)→ next-key lock
- sql执行前查询最新版本的数据,所以 InnoDB 用了 next-key lock(记录锁 + 间隙锁)。
- 它锁住查询范围和区间,防止别的事务在这个范围里插入新行。
举例:事务 A 执行,就会在 id > 2 的范围上加 next-key lock。
begin;
select *
from t_stu
where id > 2 for update
...(同时事务 B 想插入 id=5 的记录,就会被阻塞)
select *
from t_stu
where id > 2 for update
commit;
事务 B 想插入 id=5 的记录,就会被阻塞,直到事务 A 提交,从而避免幻读。
三、 为什么 “没完全解决”幻读?
场景一:(事务A快照读 + 事务B更新操作)导致幻读
- 第一次查询用的是事务A开始时的 Read View,看不到新数据。
- 事务B更新操作是 “当前读”,能看到并修改了事务 B 插入的新数据,这条新数据的 trx_id 变成了事务 A 的。
- 再次查询时,Read View 就 “看到” 了这条记录,导致前后结果不一致,发生了幻读。
场景二:先快照读,再当前读 导致幻读
- T1:事务 A 先快照读
select * from t_test where id > 100→ 3 条记录 - T2:事务 B 插入 id=200 的记录并提交
- T3:事务 A 再当前读
select * from t_test where id > 100 for update→ 4 条记录
这里的问题是:
- 第一次快照读,用的是旧的 Read View,没看到新数据。
- 第二次当前读,用的是最新数据,看到了事务 B 插入的行,前后结果集不一样,发生了幻读。
四、怎么避免这些特殊幻读?
- 尽量在事务开启后,马上执行当前读语句(如
SELECT ... FOR UPDATE)。 - 这样可以提前加上 next-key lock,锁住范围,防止其他事务插入,从根源上避免幻读。