复合索引中范围查询会截断右侧列的索引使用:1. 范围出现在最左列时,整个索引几乎失效;2. 出现在中间列时,后续列全部不可用;3. 出现在最右列时,仅该列无法利用有序性。

复合索引的B+树结构决定了范围查询后无法保持后续列有序
MySQL的复合索引底层是B+树,数据按索引定义顺序严格排序:先排第一列,第一列相同时再排第二列,以此类推。这种“嵌套排序”只在前导列值完全相等的前提下,才保证后续列有序。
一旦某列用了范围查询(如 >、、<code>BETWEEN、LIKE 'abc%'),就无法锁定一个确定的前导值区间——它会跨多个“前导值分组”,导致后续列天然失去全局有序性。
-
INDEX (a, b, c)中,a = 1 AND b > 2匹配的行可能来自(a=1,b=3)、(a=1,b=4)、(a=1,b=5)等多个子序列,而每个子序列内的c是有序的,但拼起来的全部c值(比如c=7, c=1, c=9, c=3)是乱序的 - 优化器无法用二分或指针跳跃方式快速定位
c = 5,只能对筛选出的中间结果做全量扫描 - 这不是MySQL的bug或限制,而是B+树索引能力的数学边界:范围破坏了“单一分组内有序”这个前提
WHERE子句中范围位置不同,索引利用程度完全不同
同一复合索引 INDEX (sn, name, age),不同查询条件对索引的使用差异极大,关键看范围出现在哪一列。
-
sn = 'S001' AND name = '张三' AND age > 25→sn和name走索引,age不走(范围在最后,仅截断自身) -
sn = 'S001' AND name > 'A' AND age = 25→sn走索引,name走范围扫描,age完全失效(范围在中间,后续列全丢) -
sn > 'S001' AND name = '张三' AND age = 25→ 整个索引几乎退化为全表扫描(范围在最左,后续列彻底不可用)
注意:LIKE 'xxx%' 属于等值前缀匹配,算“等值”,不触发截断;但 LIKE '%xxx' 或 LIKE '%xxx%' 会让该列索引直接失效,不参与最左前缀判断。
设计复合索引时如何规避范围导致的连锁失效
不是所有列都适合塞进同一个复合索引。优先级要按查询模式反推,而不是把高频字段堆在一起。
- 把一定会用等值查询的高选择性列放最左,例如用户ID、状态码、分类ID
- 把大概率用于范围查询的列(如时间、金额、年龄)尽量靠右,避免卡在中间“腰斩”后续列
- 如果业务中存在
WHERE a = ? AND c = ?(跳过b)和WHERE b > ? AND c = ?两种模式,不要强求一个INDEX(a,b,c)覆盖全部,宁可建两个更窄的索引:INDEX(a,c)和INDEX(b,c) - 覆盖索引(
SELECT col1,col2 FROM t WHERE ...且索引包含所有 SELECT 和 WHERE 字段)能减少回表,但不能挽救范围导致的右侧列失效
EXPLAIN里看不到“部分生效”,只会显示key_len和Extra提示
MySQL不会告诉你“前两列用了,第三列没用”。你得靠 key_len 和 Extra 字段自己推断:
- 假设
INDEX(sn,name,age)各列为VARCHAR(20)、VARCHAR(50)、TINYINT,理论最大key_len≈ 63(含长度编码开销)。若EXPLAIN显示key_len = 23,说明只用到了sn(20字节 + 3字节长度头) - 若
key_len = 75,但实际只有两列被查,很可能是字符集或排序规则导致计算偏差,需结合SHOW CREATE TABLE核对 -
Extra: Using where是中性提示;Extra: Using index表示覆盖索引;而Extra: Using index condition才表示ICP(索引条件下推),即部分过滤下推到存储引擎层——这恰恰说明有列没走索引,只是被引擎顺手筛掉了
真正容易被忽略的是:即使 key_len 看起来“够长”,只要范围出现在中间,右侧列的等值条件就只是白写,执行计划里不会报错,但性能可能差一个数量级。











