因b+树排序呈嵌套依赖:索引(a,b,c)中a全局有序、b仅在a相同时局部有序、c仅在a和b都相同时局部有序;a>10导致扫描多分支,b、c失去有序性,无法二分定位。

联合索引里范围查询为什么“砍断”右侧字段
因为B+树的排序逻辑是嵌套依赖的:索引 (a, b, c) 的数据先按 a 排序,a 相同再按 b 排,a 和 b 都相同才按 c 排。一旦 a > 10 这种范围条件出现,MySQL 就无法落在单个子树路径上,而必须扫描所有满足 a > 10 的分支——这些分支里的 b 值不再全局有序,c 更是彻底散乱。
这不是 MySQL 故意不走索引,而是 B+ 树结构本身决定了它没法对无序值做二分查找或指针跳转。
-
IN不触发截断,因为它本质是多个=,每条路径仍保持后续列有序 -
LIKE 'abc%'算等值前缀,也不截断;但LIKE '%abc'直接让该列索引失效,不参与最左匹配 -
!=、NOT IN、IS NOT NULL通常等价于全范围扫描,同样导致右侧失效
怎么一眼看出右侧字段真没走索引
别只看 EXPLAIN 的 key 字段是否显示索引名,关键盯两个指标:
-
key_len:表示实际用到的索引字节数。对照索引定义算各字段理论长度(比如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,...,99):值一多 SQL 长度爆炸,执行计划开销剧增;且若数据稀疏,b依然无法保证连续 - 用
UNION ALL拆成多个等值查询:语义等价,但维护成本高,优化器可能拒绝合并,还容易触发临时表或文件排序 - 加
FORCE INDEX:它只影响“用不用这棵树”,不改变树内搜索逻辑,c还是没法加速 -
ORDER BY a, b LIMIT 10配合WHERE a > 10:排序字段即使出现在索引里,只要WHERE中a是范围,b就无法用于索引排序,大概率触发Using filesort
真正有效的解法只有两个方向
要么调整索引设计,要么收敛查询意图:
- 把高频等值过滤的字段左移,把大概率范围查询的字段(如时间、金额)尽量靠右。例如原索引
(created_at, status)应改为(status, created_at),先筛状态,再对结果集按时间切片 - 如果业务中存在
WHERE a = ? AND c = ?(跳过b)和WHERE b > ? AND c = ?两种模式,不要强求一个INDEX(a,b,c)覆盖全部,宁可建两个更窄的索引:INDEX(a,c)和INDEX(b,c) - 覆盖索引(
SELECT和WHERE字段都在索引中)能减少回表,但不能挽救范围导致的右侧列失效
最容易被忽略的是回表放大效应:即使 WHERE a > 10 只返回 100 行,若后续 b = 20 实际只命中其中 1 行,MySQL 仍要对这 100 行全部回表取数据再过滤——当表行数大、I/O 成本高时,这个“看似小范围”的查询可能比全表扫描还慢。











