← 每日面试题 学习看板

InnoDB在RR隔离级别下如何解决幻读?

面试题/03数据库中间件/01MySQL/05事务与锁机制/InnoDB在RR隔离级别下如何解决幻读?.md

面试记录 2026-10-13

题目:InnoDB在RR隔离级别下如何解决幻读?

难度:high

标签:db/mysql/transaction, mysql/mvcc, mysql/gap-lock, difficulty/high

评分:⭐⭐⭐☆☆(3/5)

我的回答

InnoDB 在可重复读下解决幻读使用的是 MVCC,解决了部分幻读,并没有完全解决幻读。

它解决的方式是基于版本控制,当事务开启时,第一次进行 SELECT 的时候,创建对应的事务版本号,然后依据 MVCC 的机制读取事务,根据事务的 ID 去读取当前可读到的事务 ID 信息,来进行读取数据的控制,当进行了数据删除的时候,还是会导致 MVCC 中事务数据出现丢失,发生 SELECT 的返回结果不一致的问题。但是它还是能处理一部分幻读问题的。

点评

优点:

不足:

补充:

完整讲解

第一部分:知识重点剖析

核心考点定位:考察能否讲清 RR 隔离级别下"快照读靠 MVCC、当前读靠 Next-Key Lock"的双机制,并识别出"InnoDB 其实不 100% 防幻读"的边界。

知识点拆解:

  1. 幻读的定义
  1. RR 防幻读的两条路径
  1. MVCC 三要素
  1. Next-Key Lock 的构成与作用
  1. RR 不 100% 防幻读的边界
  1. 常见误区辨析

第二部分:考察方向与延伸递进

考察方向:

延伸递进面试题:

  1. RC 和 RR 在锁行为上有哪些差异?
  1. RR 下"当前事务能看到自己提交后又看到别的行"是什么情况?
  1. 如何在不影响业务的前提下测试 RR 是否真的防幻读?

第三部分:面试者参考回答(口语化)

面试官您好,InnoDB 在 RR 隔离级别下解决幻读我用双机制来答,同时也要说清它并不 100% 防。

第一个机制是快照读靠 MVCC。普通 SELECT 是快照读,事务做第一次快照读时会创建 ReadView,含 m_ids、min_trx_id、max_trx_id、creator_trx_id 四个字段,用这套规则判断当前行版本是否可见。RR 的关键是首次读后 ReadView 复用——不像 RC 每次都新建。所以事务后续再读,还是那个快照时刻的数据,其他事务的 INSERT、UPDATE、DELETE 都看不到。快照读这条路径完全不出现幻读。

第二个机制是当前读靠 Next-Key Lock。SELECT FOR UPDATE、UPDATE、DELETE 都是当前读,会加 Next-Key Lock,也就是 Record Lock + Gap Lock 的组合。Gap Lock 锁的是索引上的间隙区间(不含记录),比如锁 (id=3, id=7) 之间的间隙。其他事务想在这个间隙内插入,会被阻塞直到持锁事务提交或回滚。RR 默认加 Next-Key Lock,RC 只加 Record Lock 不加 Gap Lock——所以 RC 不防间隙插入导致的幻读,RR 大部分能防。

但要承认 InnoDB 并不 100% 防幻读,官方文档也是这么说的。典型反例是事务内混用快照读和当前读:事务 A 先用普通 SELECT 读一次走 ReadView,再用 SELECT FOR UPDATE 当前读,如果其他事务在间隙内插入并提交了,A 的当前读就能看到这个新插入的行——这不是 MVCC 的锅,是当前读本身读的是最新已提交数据。另外还有一些间隙锁范围陷阱,比如锁的是索引列的间隙而不是主键的间隙,也会让"看似幻读"的现象出现。

再补充个 RC vs RR 的差异。RC 每次快照读都新建 ReadView、RR 首次读后复用;RC 只加 Record Lock、RR 加 Next-Key Lock。所以 RC 完全不防幻读,RR 大部分防但不完美。生产上 Spring Boot 默认其实是 RC,MySQL 官方默认是 RR,两者选择看业务——读多写少、要防幻读选 RR;写多、有间隙锁性能顾虑选 RC。

当然,如果真的想 100% 防幻读,只能上 SERIALIZABLE 隔离级别,但代价是所有读都加 S 锁,串行化执行,业务基本会崩。所以工程上都是接受 RR 的"大部分防"这个折中。