复合索引中范围查询会截断后续列索引使用:where a = 1 and b > 10 and c = 20 时,仅a、b参与索引查找,c因b为范围条件而无法利用索引,这是b+树有序性决定的必然行为,非bug。

复合索引中范围查询中断后续列使用
MySQL在复合索引 (a, b, c) 上执行 WHERE a = 1 AND b > 10 AND c = 20 时,c 列无法走索引 —— 这不是bug,而是B+树结构决定的必然行为。
- B+树索引按定义顺序组织数据:先排
a,a相同再排b,b相同再排c -
a = 1可定位到一个连续块;b > 10在该块内做范围扫描,但结果已不保证c有序(不同b值下的c是分散的) - 优化器无法用索引直接跳到满足
c = 20的位置,只能回表或全索引扫描后过滤
哪些操作算“范围查询”?
不只是 > ,任何破坏后续列有序性的条件都算。容易被忽略的是:
-
BETWEEN、IN(当右侧是多个离散值时,仍视为等值,但若配合ORDER BY或LIMIT可能触发索引下推) -
!=或:优化器通常放弃走索引,因为需排除大量节点 -
IS NULL/IS NOT NULL:对非主键索引,尤其当NULL占比高时,常被判定为低选择性而跳过索引 -
LIKE 'abc%'是等值前缀匹配,b和c仍可用;但LIKE '%abc'或LIKE '%ab%'直接导致整个索引失效
如何验证是否真的失效?
别只看执行计划里有没有 key 字段,重点看 key_len 和 Extra:
-
key_len显示实际使用的索引字节数:比如(a,b,c)总长 100,若只用到前两列,key_len应明显小于100 -
Extra出现Using index condition表示用了索引下推(ICP),说明c被下推到引擎层过滤,仍算有效利用;若只有Using where,则c是Server层回表后过滤,已失效 - 用
EXPLAIN FORMAT=JSON查看used_key_parts数组,它明确列出哪些索引部分被真正使用
绕过限制的实用策略
不能总靠改SQL,得结合业务和数据分布选方案:
- 把高频等值查询列前置:比如查
status+created_at,若status只有3个值,把它放复合索引最左,created_at放后,比反过来更稳 - 拆分查询:用
IN替代范围(如b IN (11,12,13)),前提是值数量可控;或用多个UNION拆成小范围 - 冗余索引:为
(a,c)单独建索引,代价是写入开销和磁盘占用,但对读多写少场景值得 - 生成列+索引(MySQL 5.7+):如果
c实际依赖b的区间(如c是b的分类标签),可建生成列category AS (CASE WHEN b > 10 THEN 'high' ELSE 'low' END)并索引它
b > 10 后 c = 20 是否真需要索引加速,取决于 c 的选择性和数据倾斜程度 —— 这点常被忽略:即使语法上“失效”,若 c 值极稀疏,回表过滤反而比全扫快。判断必须基于 EXPLAIN + SHOW STATUS 中的 Handler_read_next 等指标,而不是凭经验断言。











