mysql的rr隔离级别并未完全避免幻读,仅在“当前读+索引+范围查询”时通过next-key lock防止幻读;快照读靠mvcc隐藏新行但不加锁,混用快照读与当前读会导致逻辑幻读。

幻读只发生在“范围查询 + 当前读”组合中
幻读不是随便查查就出现的,它有严格触发条件:必须是当前读(SELECT ... FOR UPDATE、UPDATE、DELETE),且 WHERE 条件是范围(如 id > 10、age BETWEEN 20 AND 30),同时该字段有索引。快照读(普通 SELECT)靠 MVCC 天然“看不见”新插入行,根本不会幻读——但这只是视图固定,不是锁住空白。
常见错误现象:SELECT * FROM user WHERE status = 1 执行两次结果一样,就以为 RR 防住了幻读;其实这只是复用第一次的 ReadView,如果中间穿插了 UPDATE ... WHERE status = 1,立刻切换为当前读,MVCC 失效,幻读风险就回来了。
- 没索引的
WHERE条件(比如name = 'Alice'但name无索引)→ 全表扫描 → 间隙锁退化,无法阻塞插入 - 主键等值查询(
WHERE id = 100)→ 只加记录锁,不锁间隙 → 别人仍可插入id = 99或101 - 唯一索引等值查询(
WHERE email = 'x@y.z')→ 同样只锁记录,不锁间隙 → 若业务允许重复邮箱(比如未设UNIQUE),插入同邮箱新行仍成功
加锁必须用 Next-Key Lock,不能只靠 Record Lock
单纯加行锁(Record Lock)对幻读无效,因为它只锁住已有数据行,空白间隙完全开放。InnoDB 在 RR 下默认对范围查询启用 Next-Key Lock,即 Record Lock + Gap Lock 组合——前者锁数据,后者锁“两行之间”的空白区间。
例如表中有 id 值:1、5、10,执行 SELECT * FROM t WHERE id > 3 FOR UPDATE,实际锁定的是:(3,5] 和 (5,10] 两个区间。此时插入 id = 4 或 id = 7 都会被阻塞。
-
Gap Lock不锁数据,只锁间隙;多个事务对同一间隙加Gap Lock是兼容的(不互斥),但和INSERT互斥 - 间隙范围是左开右闭,如
(5,10]表示允许插入id = 5(已存在则报唯一键冲突),但禁止插入6~10之间的值 - 若查询条件命中空范围(如
WHERE id > 100且最大id是 50),则锁住(50,+∞),新插入任何大于 50 的id都会被拦
容易踩的坑:间隙锁被悄悄禁用或失效
生产环境最常翻车的地方不是不懂锁,而是配置或 SQL 写法让间隙锁压根没生效。
-
innodb_locks_unsafe_for_binlog = 1(MySQL 5.6+ 已废弃,但旧配置残留)→ 直接禁用间隙锁,RR 退化为类似 RC,主从可能不一致 - 事务中混用快照读和当前读:先
SELECT(快照读,生成旧ReadView),再UPDATE ... WHERE x > 10(当前读,走最新数据+加锁),最后又SELECT(仍快照读)→ 三次结果可能完全不同,业务逻辑崩坏 -
INSERT ... SELECT或REPLACE INTO这类语句隐式触发当前读,但开发者常忽略其锁行为,导致意外阻塞 - 使用
SELECT ... LOCK IN SHARE MODE而非FOR UPDATE时,虽然也加 Next-Key Lock,但若后续没有写操作,其他事务仍可能通过INSERT绕过(需结合业务判断是否足够)
验证间隙锁是否生效的实操方法
别猜,直接看 INFORMATION_SCHEMA.INNODB_TRX 和 INNODB_LOCKS(MySQL 8.0+ 用 PERFORMANCE_SCHEMA.DATA_LOCKS)。
执行完加锁语句后,运行:
SELECT trx_id, trx_state, trx_query FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_state = 'LOCK WAIT';
再查锁详情:
SELECT ENGINE_TRANSACTION_ID AS trx_id, OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_DATA FROM PERFORMANCE_SCHEMA.DATA_LOCKS WHERE OBJECT_SCHEMA = 'your_db' AND OBJECT_NAME = 'your_table';
- 若
LOCK_MODE显示RECORD,说明只加了记录锁(等值查询或无索引) - 若显示
RECORD,GAP或NEXT-KEY,说明间隙锁已生效 - 若
LOCK_DATA是具体值(如5),是记录锁;若是区间(如5, 10),就是间隙锁作用范围
真正难的不是知道要加锁,而是确认锁加在哪儿、加得够不够——很多幻读问题,根源在于你以为锁住了,其实锁早被绕开了。











