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 UPDATE、SELECT ... LOCK IN SHARE MODE、UPDATE、DELETE,走 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 级别更强的原因。

相关推荐
kobe_OKOK_24 分钟前
mysql 开启 binlog 日志
数据库·mysql
这个DBA有点耶1 小时前
MySQL 8.0直方图:统计信息升级了,执行计划为什么还是选错?
数据库·mysql·代码规范
aiks_yuan6 小时前
MySQL 深分页 LIMIT 越翻越慢怎么办:300 万行实测 OFFSET、延迟关联和 keyset 的耗时差距(MySQL 8.2)
mysql
Decay__6 小时前
14_MySQL 高可用与容灾架构 —— MGR / InnoDB Cluster / Keepalived+VIP / 双主 / 读写分离 / 两地三中心
运维·数据库·sql·mysql·架构
Thanks_ks9 小时前
MySQL 的索引是什么?用图书馆的目录讲明白
数据库·sql·mysql·索引·联合索引·explain·慢查询优化
.痞老板.1 天前
mysql初阶--增删改查操作
数据库·mysql
浮生若城1 天前
MySQL:库与表的操作
数据库·mysql
翔云1234562 天前
RocksDB中,客户端每写入一次数据,需要访问几次磁盘
运维·数据库·mysql
在多学一点2 天前
MYSQL第二次作业
android·数据库·mysql
yeccheng2 天前
MySQL 索引超详解:从底层 B+ 树到面试高频考点
java·开发语言·jvm·mysql·面试