mysql rr隔离级别不能单靠设置解决幻读,必须满足“快照读+mvcc”或“当前读+索引+next-key lock”;快照读依赖read view固化可见版本,当前读则需范围查询、b+树索引和加锁语句协同生效。

MySQL InnoDB 的 REPEATABLE READ 隔离级别**不能单靠设置就解决幻读**,它只在特定组合下才真正防住——必须同时满足「快照读 + MVCC」或「当前读 + 索引 + next-key lock」,缺一不可。
普通 SELECT 为什么看不到新插入的行?
因为它是快照读,依赖 MVCC 生成的 Read View。事务第一次执行 SELECT 时就固化了可见版本,后续所有普通查询都复用这个视图,undo log 里找得到的旧版本才可见,新插入的行没提交或提交了但不在该视图时间点之后,自然查不到。
- 这能屏蔽绝大多数幻读,但仅限于不加锁的
SELECT - 一旦换成
UPDATE、DELETE或SELECT ... FOR UPDATE,立刻切换为当前读,MVCC 失效 - 如果此时没索引或条件不走索引,InnoDB 无法加间隙锁,新行就能插进来
SELECT ... FOR UPDATE 为什么会失效?
很多人以为只要加了 FOR UPDATE 就万事大吉,其实不然。next-key lock 是否生效,取决于三个硬性条件:
- WHERE 条件字段**必须有 B+ 树索引**(主键、唯一索引、二级索引都行,但函数索引、前缀索引不完整匹配就不行)
- 必须是**范围查询**(如
WHERE id > 100、WHERE name BETWEEN 'a' AND 'z'),等值查询(WHERE id = 5)只锁单行及其前隙,不锁整个区间 - 语句必须是**当前读**(
SELECT ... FOR UPDATE、UPDATE、DELETE),普通SELECT不触发
例如:SELECT * FROM t WHERE status = 1 FOR UPDATE,若 status 无索引,InnoDB 会全表扫描,间隙锁退化为表级锁或干脆不加,插入仍可成功。
为什么唯一索引的等值查询还可能幻读?
InnoDB 对唯一索引的等值查询(如 WHERE id = 100)通常只加 record lock,不加 gap lock —— 因为理论上“值唯一”,插入重复值会被主键/唯一约束拦截,无需锁间隙。但这带来一个漏洞:
- 如果插入的是**非唯一字段的新值**(比如
WHERE name = 'alice',而name是普通索引),且该值当前不存在,InnoDB 可能只锁已有记录,不锁 name='alice' 对应的间隙 - 另一个事务刚好插入
name = 'alice'的新行,就会绕过锁,造成幻读 - 低基数字段(如状态枚举)上建的索引,InnoDB 有时会退化为锁整段索引区间,影响并发,但未必覆盖所有插入点
最容易被忽略的坑:快照读后写入
这是生产环境高频出问题的场景:事务先用普通 SELECT 查不到某条记录(快照里没有),然后直接 INSERT;但另一事务在中间插入了同条件数据并提交,导致两次操作逻辑冲突。
- 快照读查不到 → 误判“数据不存在” → 执行插入 → 违反业务唯一性约束或产生脏数据
- MVCC 保证了读一致,但不保证“读-写”逻辑原子性
- 这种场景必须改用
SELECT ... FOR UPDATE+ 显式判断,或用INSERT ... ON DUPLICATE KEY UPDATE做幂等处理
真正防幻读不是调个隔离级别完事,而是得盯住查询类型、索引结构、锁行为三者是否咬合。任何一环松动,幻读就从缝隙里钻出来。











