联合索引中范围查询(如a > 10)会导致后续列(b、c等)失效,因B+树中后续列不再全局有序,无法定位;可通过key_len和Extra中Using where验证失效程度。

因为联合索引的 B+ 树结构决定了:一旦某列用了 >、、<code>BETWEEN 等范围条件,后续字段就无法再利用有序性做快速定位——这不是 bug,是树的遍历逻辑本身决定的。
Range 查询为什么直接“切断”右侧索引
联合索引 idx(a, b, c) 的 B+ 树是按字典序严格排序的:先排 a,a 相同再排 b,a 和 b 都相同才排 c。这种嵌套顺序只在“等值路径”上成立。
当出现 a > 10 时,MySQL 不再能跳到某个确定节点,而必须扫描所有 a > 10 对应的子树分支。这些分支里,b 值不再全局有序(比如 a=11 下的 b=5 和 a=12 下的 b=5 可能相距很远),c 更是彻底无序。优化器无法用 b = 20 或 c = 'x' 做索引内跳转,只能回表后逐行过滤。
-
IN不触发右侧失效,因为它本质是多个=,每条路径仍保持b、c有序 -
>=和虽带“等号”,但只要涉及非单点范围(如 <code>a >= 10),就和a > 9等价,同样截断 -
!=、NOT IN、IS NOT NULL通常等效于全范围扫描,也会让右侧失效
怎么确认右侧字段真失效了
别只看 EXPLAIN 的 key 字段是否显示索引名,关键看两个指标:
-
key_len:它表示实际用到的索引字节数。对照索引定义算出各字段理论长度(比如INT是 4 字节,VARCHAR(50)在 utf8mb4 下最多 200 字节),就能反推出用到了第几个字段 -
Extra中是否出现Using where:如果写了WHERE a = 1 AND b > 2 AND c = 3,但Extra是Using where而非Using index,说明c = 3是回表后过滤的,没走索引
例如索引为 (a INT, b VARCHAR(50), c DATETIME),查 a = 1 AND b > 10 AND c = '2023-01-01' 时,若 key_len = 24(刚好够 a + b),就证明 c 没参与索引查找。
哪些“绕开”写法其实没用
很多人试图用语法变形规避,但多数只是徒增复杂度,不解决根本问题:
- 把
a > 10拆成a IN (11,12,13,...,100):值一多,SQL 长度爆炸,执行计划开销剧增;且若数据稀疏,b依然无法保证连续 - 用
UNION ALL拆成多个等值查询:语义等价,但维护成本高,优化器可能拒绝合并,还容易触发临时表或文件排序 - 加
FORCE INDEX:它只影响“用不用这棵树”,不改变树内搜索逻辑,c还是没法加速 -
ORDER BY a, b LIMIT 10配合WHERE a > 10:排序字段即使出现在索引里,只要WHERE中a是范围,b就无法用于索引排序,大概率触发Using filesort
真正有效的解法只有两个方向
要么调整索引设计,要么收敛查询意图:
- 把高频等值过滤的字段左移,把大概率范围查询的字段右移。例如原索引
(created_at, status),created_at经常>查询,导致status失效;应改为(status, created_at),先筛状态,再对结果集切时间片 - 用覆盖索引减少回表。如果查询只需
a和b,就建idx(a, b)并SELECT a, b,避免触碰c引发的额外 I/O 放大 - 如果业务允许,把原本范围查的字段改成等值(比如用分区表按时间分片,再查具体分区),但这需要架构配合,不是纯 SQL 层能解决的
最容易被忽略的是回表放大效应:即使 a > 10 只返回 100 行,若其中只有 1 行满足 b = 20,MySQL 仍要对这 100 行全部回表取数据再过滤——当表很大、I/O 昂贵时,这比全表扫描还慢。


















