mysql的innodb引擎对可重复读做了那些优化,可以避免幻读

3 阅读4分钟

一、先理解:什么是幻读?

幻读指的是在同一个事务中,连续执行两次相同的范围查询,第二次查询会返回第一次查询中没有的 “新插入” 数据,导致事务看到了 “幻觉” 数据。举个例子:

  1. 事务 A 执行 SELECT * FROM t WHERE id > 10;,返回 0 条数据;
  2. 事务 B 插入一条 id=15 的数据并提交;
  3. 事务 A 再次执行相同的查询,却返回了 id=15 的数据 —— 这就是幻读。

二、InnoDB 针对 RR 避免幻读的核心优化

InnoDB 并非简单依赖 MVCC,而是通过 锁机制 + MVCC 的组合优化,从 “读” 和 “写” 两个维度彻底解决幻读:

1. 核心锁机制:Next-Key Lock(临键锁)(写操作防幻读)

这是 InnoDB 为 RR 级别新增的核心锁优化,是解决幻读的关键。

  • 定义:Next-Key Lock 是 记录锁(Record Lock) + 间隙锁(Gap Lock) 的组合,既锁定当前行记录,也锁定记录之间的 “间隙”,防止其他事务在间隙中插入数据。
  • 作用:当执行范围更新 / 删除(如 UPDATE t SET ... WHERE id > 10)时,InnoDB 不会只锁已存在的行,而是锁整个 “范围”(包括不存在的间隙),彻底阻断其他事务插入该范围的新数据。

示例验证

-- 事务 A(RR 级别)
BEGIN;
-- 范围更新,触发 Next-Key Lock
UPDATE t SET name = 'test' WHERE id > 10;

-- 事务 B
BEGIN;
-- 尝试插入 id=15 的数据,会被阻塞(直到事务 A 提交/回滚)
INSERT INTO t (id, name) VALUES (15, 'new'); -- 阻塞!

正是因为 Next-Key Lock 锁住了 id>10 的所有间隙,事务 B 无法插入新数据,从根源上避免了幻读。

2. MVCC 多版本控制(读操作防幻读)

InnoDB 的 MVCC(多版本并发控制)是 RR 级别 “可重复读” 的基础,也辅助解决了读层面的幻读:

  • 原理:每个事务有自己的 事务 ID(trx_id) ,每行数据会记录创建它的事务 ID(DB_TRX_ID)和删除 / 更新它的事务 ID(DB_ROLL_PTR)。
  • 核心优化:RR 级别下,事务启动时会生成一个 一致性视图(Read View) ,后续所有查询都基于这个视图读取数据 —— 即只读取 “事务启动前已提交” 的数据,无视后续其他事务插入 / 提交的新数据。

示例验证

-- 事务 A(RR 级别)
BEGIN;
-- 第一次查询,返回 0 条
SELECT * FROM t WHERE id > 10; -- 结果:空

-- 事务 B
BEGIN;
INSERT INTO t (id, name) VALUES (15, 'new');
COMMIT;

-- 事务 A 再次查询(同一条 SQL)
SELECT * FROM t WHERE id > 10; -- 结果:依然空(无幻读)

因为事务 A 的 Read View 在启动时就固定了,即使事务 B 插入并提交了新数据,事务 A 也看不到,避免了读层面的幻读。

3. 针对唯一索引的优化:降级为记录锁

Next-Key Lock 虽然能防幻读,但会增加锁的粒度,可能导致并发降低。InnoDB 做了智能优化:

  • 当查询条件基于 唯一索引(如主键) 且是 “等值查询” 时,Next-Key Lock 会自动降级为 Record Lock(记录锁) ,只锁定匹配的行,不锁定间隙。
  • 原因:唯一索引的等值查询不会有 “间隙插入导致幻读” 的问题(唯一键保证了数据唯一),无需锁间隙。

示例

-- 主键(唯一索引)等值更新,仅锁 id=10 的行,不锁间隙
UPDATE t SET name = 'test' WHERE id = 10;
-- 其他事务可插入 id=11、id=9 的数据,不阻塞

4. 快照读 vs 当前读的区分

InnoDB 对 “读操作” 做了细分,进一步优化幻读问题:

  • 快照读(Snapshot Read) :普通 SELECT 语句,走 MVCC,基于 Read View 读历史版本,无锁,避免读幻读;
  • 当前读(Current Read)SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODEUPDATEDELETE,走 Next-Key Lock,锁定当前数据和间隙,避免写幻读。

三、关键补充:RR 级别 vs 幻读的完整逻辑

表格

操作类型优化手段防幻读效果
普通 SELECT(快照读)MVCC + Read View看不到事务启动后插入的新数据
范围更新 / 删除(当前读)Next-Key Lock(临键锁)阻塞其他事务插入范围內的新数据
唯一索引等值操作降级为 Record Lock保证并发的同时,不产生幻读

总结

InnoDB 针对 RR 级别避免幻读的核心优化可总结为 3 点:

  1. Next-Key Lock 临键锁:范围写操作时锁定行 + 间隙,阻断其他事务插入新数据(核心防写幻读);
  2. MVCC + Read View:事务启动时生成一致性视图,普通查询只读历史版本(核心防读幻读);
  3. 智能锁降级:唯一索引等值操作时,临键锁降级为记录锁,平衡幻读防护与并发性能。

简单来说:RR 级别下,InnoDB 用 “锁堵写、视图控读” 的组合策略,彻底解决了幻读问题,这也是 MySQL RR 级别比其他数据库(如 Oracle)RR 级别更强的原因。