非唯一索引范围查询锁得比主键宽,是因为InnoDB无法唯一定位边界,必须向右多探一个索引项以防遗漏重复值,所有扫描到的索引区间均保持next-key lock,如(25,30]、(30,35]甚至(35,+∞)。

非唯一索引范围查询为什么锁得比主键还宽?
因为InnoDB无法靠键值唯一定位边界,必须向右多探一个索引项,以防遗漏重复值。比如WHERE age BETWEEN 25 AND 30 FOR UPDATE,即使当前页最大age是30,它仍要确认下一页有没有另一个age = 30——这个“不确定”,就导致锁范围被迫延伸。
实际加锁范围怎么算?看访问路径,不是看WHERE条件
加锁只发生在「查找过程中真正访问到的索引节点」上,且每个访问点都按next-key lock(前开后闭)起手,再视情况退化。非唯一索引范围查询时:
- 不会触发
优化1(等值+唯一索引→记录锁),因为范围查询本身不满足“等值”前提 - 也不会触发
优化2(向右遍历遇不匹配→退化为间隙锁),因为范围查询的终点不是“第一个不满足的值”,而是“所有可能满足的值及之后一个” - 最终结果:所有扫描到的索引区间都保持
next-key lock,包括(25,30]、(30,35]甚至(35,+∞)(若35是扫描到的最后一个值)
为什么EXPLAIN显示rows少,但INSERT却被卡住?
因为EXPLAIN只估算匹配行数,不反映间隙锁覆盖范围。真实阻塞往往来自未被WHERE显式命中、却被扫描路径带进来的间隙。验证方法:
- 查
performance_schema.data_locks,过滤LOCK_MODE含GAP或NEXT-KEY的行 - 执行
INSERT INTO t (age, ...) VALUES (26, ...),若被阻塞,说明(25,30)已被锁 - 注意:哪怕表里当前没有
age = 26,只要间隙被锁,插入就失败
唯一索引能退化,非唯一索引不能退化的根本原因
唯一索引的键天然排他,InnoDB扫到id = 30后,看到下一个id = 35,立刻知道id > 30 AND id 之间不可能有数据;而非唯一索引的<code>age = 30后面,完全可能还有另一个age = 30(不同主键)。这个“重复可能性”就是它不敢停、不敢降级的底层约束。


















