非唯一索引等值查询必然带间隙锁,因为innodb必须锁定“值所在区间”防止幻读,相同键值可能被反复插入,仅单行锁无法保证一致性,故需封锁前后间隙。

非唯一索引等值查询为什么必然带间隙锁
因为InnoDB必须靠锁住「值所在区间」来防止幻读,而非唯一索引无法仅靠单行锁定保证一致性——相同键值可能被反复插入,所以必须封锁前后间隙。比如表有普通索引idx_name,数据为('a', 'b', 'd'),执行SELECT * FROM t WHERE name = 'c' FOR UPDATE时,'c'不存在,InnoDB会定位到'b'和'd'之间,加间隙锁(b, d)。此时另一个事务执行INSERT INTO t (name) VALUES ('c'),需申请插入意向锁,与该间隙锁冲突,直接阻塞。
非唯一索引范围查询会扩大扫描边界
WHERE条件走非唯一索引做范围扫描(如WHERE status BETWEEN 'pending' AND 'processing')时,InnoDB按next-key lock机制逐节点加锁:每访问一个索引项,就锁住它和下一个值之间的开区间。若索引中实际值是(1, 5, 10, 15),扫描到15后还需确认下一个节点是否存在,可能继续读到20,于是连带把(15, 20)也锁住——哪怕业务只关心10以内的数据。
容易踩的坑包括:
- 隐式类型转换导致索引失效,退化为全表扫描,锁住所有聚簇索引记录
-
WHERE DATE(create_time) = '2026-01-01'这类函数操作,使索引无法下推,间隙无法精确定界 - 联合索引顺序错,比如建了
(created_at, user_id)却查WHERE user_id = ?,优化器拒绝使用,触发全索引扫描
唯一索引能退化为记录锁,非唯一索引不能
同样是WHERE id = 100,如果id是主键或定义为UNIQUE,InnoDB只加一条记录锁;但若只是普通索引(哪怕值唯一、没重复),也会加next-key lock——即行锁+左侧间隙锁。这个差异不是优化器选择,而是存储引擎硬编码逻辑:只要索引非唯一,等值查询就必须覆盖潜在插入点。
验证方式很简单:
- 用
SELECT @@tx_isolation确认是REPEATABLE-READ - 查
performance_schema.data_locks,非唯一索引查询会同时看到lock_mode S在二级索引上 +lock_mode S,REC_NOT_GAP在主键上,外加隐式间隙锁 - 而唯一索引查询只显示单条
lock_mode X或S记录锁,无gap before rec
没索引时更糟:直接锁全表
WHERE条件完全不走索引(如WHERE extra_info LIKE '%abc%'且该字段无索引),InnoDB只能全表扫描聚簇索引,为每一行加record lock + gap lock,效果等同于锁整张表。这不是性能问题,是隔离性兜底策略——宁可多锁,也不能漏锁。
这种场景下,FORCE INDEX无效,LIMIT也不起作用:MySQL仍会扫描全部匹配候选行再截断,所有扫描路径上的间隙都被锁定。
真正有效的解法只有两个:
- 为高频查询字段补联合索引,且列顺序严格匹配WHERE条件最左前缀
- 把非唯一索引升级为联合唯一索引(如
(status, id)),让等值查询退化为record lock
间隙锁本身不可绕过,但它的影响范围完全取决于你是否能让InnoDB精准定位——而精准定位的前提,永远是索引结构与查询模式严丝合缝。











