非唯一二级索引范围查询触发间隙锁失效,是因为索引未被实际使用(如无索引、隐式类型转换),导致innodb退化为全表或全索引扫描,无法精确定界间隙,gap lock失效,幻读发生;需通过explain确认type为range/ref且key正确才能保障next-key lock生效。

为什么非唯一二级索引查范围会触发间隙锁失效?
因为 InnoDB 对非唯一二级索引执行范围查询(如 WHERE name BETWEEN 'a' AND 'z')时,必须用 Next-Key Lock 锁住索引项及其前隙——但若该索引不唯一,且查询条件又没走索引(比如 name 字段无索引或隐式类型转换),MySQL 就会退化为全表扫描或全二级索引扫描,此时间隙无法精确定界,Gap Lock 实际失效,插入仍能成功,幻读就漏出来了。
怎么确认你的查询真走了非唯一二级索引?
别靠猜,直接 EXPLAIN 看执行计划:
-
type是range或ref,且key显示你期望的二级索引名 → 走了索引 -
type是ALL或index(扫描整个二级索引 B+ 树叶子节点)→ 没有效利用间隙,风险极高 -
Extra出现Using where; Using index是理想状态;出现Using temporary; Using filesort通常意味着锁粒度失控
非唯一二级索引下如何安全加锁防幻读?
普通 SELECT 不加锁,必须显式升级为当前读:
- 对范围查询,统一用
SELECT ... FOR UPDATE,而不是SELECT ... LOCK IN SHARE MODE—— 后者在某些版本中对间隙锁支持较弱 - 确保 WHERE 条件能命中该二级索引,例如
WHERE status = 'pending' AND created_at > '2026-06-01',且status是非唯一二级索引前缀 - 避免在该字段上做函数操作,如
WHERE UPPER(name) = 'ALICE',这会让索引失效,间隙锁形同虚设 - 如果业务允许,把非唯一索引改为联合唯一索引(如
(status, id)),等值查询时只加Record Lock,更轻量且无间隙争议
INSERT ON DUPLICATE KEY UPDATE 能绕过幻读吗?
不能直接绕过,但能规避“先查后插”逻辑带来的幻读窗口:
-
INSERT ... ON DUPLICATE KEY UPDATE在插入阶段就会触发Next-Key Lock,对目标范围加锁,其他事务无法在间隙插入 → 实质是把幻读防控提前到写入侧 - 前提是冲突字段必须是**唯一索引或主键**;如果仅依赖非唯一二级索引,
ON DUPLICATE KEY不生效,还会报错ERROR 1062 - 慎用
REPLACE INTO:它本质是DELETE + INSERT,会释放原记录锁再加新锁,中间有极短窗口可能被并发插入钻空子
最易被忽略的一点:即使开了 RR 隔离级别,只要查询没走索引、或者用了 OR 导致索引合并(index merge),InnoDB 就可能分步加锁或降级锁粒度,幻读隐患依然存在。盯死 EXPLAIN 输出,比调隔离级别管用得多。











