Range查询会切断右侧索引,因为联合索引 idx(a,b,c) 的B+树按字典序嵌套排序,仅在等值路径(如a=1且b=2)上保证b、c有序;一旦出现a>10等范围条件,a后续分支中b不再全局有序、c彻底无序,优化器无法用b=20或c='x'进行索引内跳转,只能回表逐行过滤。

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
真正有效的解法只有两个方向
要么调整索引设计,要么收敛查询意图:
- 把高频等值过滤的字段(如状态码、租户ID、类型标识)放在最左
- 把大概率参与范围查询的字段(如时间、金额、评分)放在中间或偏右
- 把仅用于排序或覆盖查询的字段(如需要
SELECT的列)放在最右——它们本就不承担过滤职责 - 如果某字段既常被等值查、又偶尔被范围查,优先按等值场景排;若范围查询频次更高,就把它右移,同时确认左侧字段能否单独支撑大部分查询
最易被忽略的是:索引字段顺序不是由“选择性高低”决定的,而是由“查询模式中哪一列更可能先被范围化”决定的。哪怕 created_at 选择性极高,只要它常被 > 查询,就不该放在联合索引最左。


















