复合索引中范围查询“切断”右侧字段索引能力是b+树嵌套有序性决定的必然行为:当a=1时b、c有序,但a>10导致匹配多组a值,各组内b、c不全局有序,无法二分查找,故右侧字段失效。

复合索引中范围查询为什么“切断”右侧字段的索引能力
这不是 MySQL 的 bug,而是 B+ 树结构决定的必然行为:索引 INDEX(a, b, c) 的数据按字典序排列,即先严格排序 a,a 相同再排 b,a 和 b 都相同才排 c。这种嵌套有序性只在“前导列值完全相等”的路径上成立。
一旦出现范围条件(如 a > 10、b BETWEEN 5 AND 15、c LIKE 'x%'),优化器就无法定位到一个连续的子树区间——它必须扫描多个“前导值分组”,而这些分组内的 b 或 c 值不再全局有序。比如 a = 1 AND b > 2 匹配的行可能来自 (a=1,b=3)、(a=1,b=4)、(a=1,b=5) 等不同分支,拼起来的 c 值是散乱的,没法二分查找。
所以不是“MySQL 拒绝用”,而是“B+ 树根本不能支持”。c = '2023-01-01' 在这种场景下只能回表后逐行过滤。
怎么确认某个查询里右侧字段真没走索引
别只看 EXPLAIN 的 key 字段是否显示索引名,关键盯两个指标:
-
key_len:表示实际用到的索引字节数。对照索引定义算理论长度(例如INT是 4 字节,VARCHAR(50)在utf8mb4下最多 200 字节),就能反推出用到了第几个字段。若INDEX(a INT, b VARCHAR(50), c DATETIME)查询WHERE a = 1 AND b > 10 AND c = '2023-01-01',key_len = 24(刚好够a + b),说明c没参与索引查找 -
Extra中是否出现Using where:如果写了c = 'x'却看到Using where而非Using index,基本就是回表后过滤,没走索引
哪些“绕开”写法其实无效
很多尝试只是徒增复杂度,并不改变 B+ 树的遍历逻辑:
- 把
a > 10拆成a IN (11,12,13,...,100):值一多,SQL 长度爆炸;且若数据稀疏,b依然无法保证物理连续 - 用
UNION ALL拆多个等值查询:语义等价,但维护成本高,优化器可能拒绝合并,还容易触发临时表或Using filesort - 加
FORCE INDEX:它只影响“用不用这棵树”,不改变树内搜索逻辑,c还是没法加速 -
ORDER BY a, b LIMIT 10配合WHERE a > 10:排序字段即使出现在索引里,只要WHERE中a是范围,b就无法用于索引排序
真正有效的应对方向只有两个
要么调整索引设计,要么收敛查询意图:
- 把高频等值过滤的字段左移,把大概率范围查询的字段(如时间、金额)尽量靠右,避免卡在中间“腰斩”后续列
- 如果业务同时存在
WHERE a = ? AND c = ?(跳过b)和WHERE b > ? AND c = ?,不要强求一个INDEX(a,b,c)覆盖全部,宁可建两个更窄的索引:INDEX(a,c)和INDEX(b,c) -
LIKE 'xxx%'属于前缀匹配,算等值,不触发截断;但LIKE '%xxx'或LIKE '%xxx%'会让该列索引直接失效,不参与最左前缀判断
最常被忽略的是:范围位置不同,索引利用程度天差地别。同一个 INDEX(sn, name, age),sn = ? AND name > ? AND age = ? 和 sn > ? AND name = ? AND age = ? 的执行效率可能相差一个数量级——前者还能用上 sn,后者几乎退化为全表扫描。











