基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

InnoDB 在 可重复读(RR)隔离级别下,是如何解决幻读问题的?

知识点图片

在 MySQL 的 InnoDB 存储引擎中,可重复读(Repeatable Read, 简称 RR)隔离级别下解决幻读问题,并不是靠单一机制完成的,而是根据不同的读取方式(快照读 vs 当前读),采用了两种不同的机制

简单来说,核心结论是:

  1. 针对 快照读(普通 SELECT),通过 MVCC(多版本并发控制) 解决。
  2. 针对 当前读(加锁的 SELECT、UPDATE、DELETE),通过 Next-Key Lock(临键锁 = 记录锁 + 间隙锁) 解决。

以下是详细的原理解析:


1. 什么是幻读?

幻读是指:事务 A 按照某个条件查询出一些记录,此时事务 B 插入(Insert) 了一条符合该条件的新记录并提交。事务 A 再次以相同的条件进行查询时,发现多出了原本不存在的“幻影”记录。


2. 快照读(Snapshot Read)下的解决方案:MVCC

快照读指的是不加锁的普通查询,例如:SELECT * FROM table WHERE ...

在 RR 隔离级别下,InnoDB 使用 MVCC(多版本并发控制) 来防止幻读:

  • Read View(读视图)的生成时机:在 RR 级别下,事务在第一次执行普通的 SELECT 语句时,会生成一个 Read View。整个事务期间,所有的普通 SELECT 都会复用这同一个 Read View。
  • 版本可见性规则:Read View 记录了当前系统中活跃的事务 ID。当事务读取数据时,会根据记录中的隐藏字段(事务 ID)与 Read View 进行比对。
  • 解决幻读的过程
    如果事务 A 执行了第一次查询,生成了 Read View。随后事务 B 插入了一条新数据并提交。当事务 A 再次执行相同的查询时,由于新插入的数据的事务 ID 比事务 A 的 Read View 中的上限还要新,根据 MVCC 规则,这条新数据对事务 A 是不可见的。因此,事务 A 两次查询的结果一致,没有发生幻读。

3. 当前读(Current Read)下的解决方案:Next-Key Lock

当前读指的是读取最新版本数据并且加锁的操作,例如:

  • SELECT * FROM table WHERE ... FOR UPDATE
  • SELECT * FROM table WHERE ... LOCK IN SHARE MODE
  • UPDATE ... / DELETE ... / INSERT ...

在当前读下,MVCC 无法阻止其他事务插入数据。为了防止幻读,InnoDB 引入了 Next-Key Lock(临键锁)

Next-Key Lock = Record Lock(记录锁) + Gap Lock(间隙锁)

  • 记录锁:锁住具体的索引记录。
  • 间隙锁:锁住两个索引记录之间的间隙,防止其他事务在这个间隙里插入新数据。

解决幻读的过程
假设表中有 id 为 5, 10, 15 的记录。
事务 A 执行:SELECT * FROM table WHERE id > 8 FOR UPDATE;
此时,InnoDB 不仅会给 id=10 和 id=15 的记录加上记录锁,还会给 (5, 10)、(10, 15) 以及 (15, +∞) 的区间加上间隙锁
如果此时事务 B 想要插入一条 id=12 的新记录,会被间隙锁阻塞,直到事务 A 提交。因为事务 B 根本无法插入新数据,所以事务 A 再次查询时,也就不会看到所谓的“幻影”记录。


4. 进阶探讨:InnoDB 的 RR 彻底解决幻读了吗?

这是一个高频面试题。答案是:没有完全解决,在特定场景下仍然会发生幻读

由于快照读和当前读的机制不同,如果在一个事务中混合使用这两种读,或者对“幻影”记录进行了修改,就会触发幻读。

典型的幻读发生场景:

场景一:先快照读,后当前读

  1. 事务 A 执行普通 SELECT 查询 id=5 的记录,发现不存在(快照读,生成 Read View)。
  2. 事务 B 插入一条 id=5 的记录,并提交。
  3. 事务 A 执行 SELECT * FROM table WHERE id=5 FOR UPDATE(变为当前读)。此时会去读取最新版本,结果查到了 id=5 的记录。发生幻读。

场景二:更新了“幻影”记录

  1. 事务 A 执行普通 SELECT 查询 id=5 的记录,发现不存在。
  2. 事务 B 插入一条 id=5 的记录,并提交。
  3. 事务 A 执行 UPDATE table SET name = 'test' WHERE id = 5;(UPDATE 是当前读,且修改成功,因为 B 已经提交)。
  4. 由于事务 A 修改了这条记录,这条记录的事务 ID 变成了事务 A 的 ID。
  5. 事务 A 再次执行普通 SELECT 查询,根据 MVCC 规则,自己修改的数据对自己是可见的,于是查出了 id=5 的记录。发生幻读。

总结

  • 普通查询(快照读):靠 MVCC 解决幻读。
  • 加锁查询/修改(当前读):靠 Next-Key Lock(间隙锁+记录锁) 解决幻读。
  • 注意点:在同一个事务中,如果从快照读切换到当前读,或者去修改了其他事务插入的不可见记录,依然会产生幻读现象。要绝对避免幻读,可以在事务一开始就使用 FOR UPDATE 加锁(强制走当前读和间隙锁),或者将隔离级别提升到 SERIALIZABLE(串行化)。
00:00
00:00