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

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

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

  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 不会只锁已存在的行,而是锁整个 "范围"(包括不存在的间隙),彻底阻断其他事务插入该范围的新数据。

示例验证

sql 复制代码
-- 事务 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) ,后续所有查询都基于这个视图读取数据 ------ 即只读取 "事务启动前已提交" 的数据,无视后续其他事务插入 / 提交的新数据。

示例验证

sql 复制代码
-- 事务 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(记录锁) ,只锁定匹配的行,不锁定间隙。
  • 原因:唯一索引的等值查询不会有 "间隙插入导致幻读" 的问题(唯一键保证了数据唯一),无需锁间隙。

示例

ini 复制代码
-- 主键(唯一索引)等值更新,仅锁 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 级别更强的原因。

相关推荐
于眠牧北4 天前
MySQL的锁类型,表锁,行锁,MVCC中所使用的临键锁
mysql
Turnip12025 天前
深度解析:为什么简单的数据库"写操作"会在 MySQL 中卡住?
后端·mysql
加号36 天前
windows系统下mysql多源数据库同步部署
数据库·windows·mysql
シ風箏6 天前
MySQL【部署 04】Docker部署 MySQL8.0.32 版本(网盘镜像及启动命令分享)
数据库·mysql·docker
WeiXin_DZbishe6 天前
基于django在线音乐数据采集的设计与实现-计算机毕设 附源码 22647
javascript·spring boot·mysql·django·node.js·php·html5
爱可生开源社区6 天前
MySQL 性能优化:真正重要的变量
数据库·mysql
小马爱打代码6 天前
MySQL性能优化核心:InnoDB Buffer Pool 详解
数据库·mysql·性能优化
风流 少年6 天前
mysql mcp
数据库·mysql·adb
西门吹雪分身7 天前
mysql之数据离线迁移
数据库·mysql