范围查询使右侧索引失效是b+树结构决定的必然行为,因范围操作破坏后续字段的全局有序性,导致无法二分查找;in属等值集合,通常不触发右侧失效。

复合索引中一旦出现范围查询(BETWEEN、>、、<code>IN 等),其右侧所有列的索引将无法被使用——这不是 MySQL 的 bug,而是 B+ 树结构决定的必然行为。
为什么范围条件会让右边列索引失效
假设你建了复合索引 idx_a_b_c(字段顺序为 a, b, c),B+ 树叶子节点按 (a,b,c) 元组整体排序。当查询写成 WHERE a = 1 AND b > 10 AND c = 'x' 时:
-
a = 1可以快速定位到所有a=1的子树段 -
b > 10在该子树内继续筛选,但结果是一段连续区间(比如b=11,12,13...),而每个b值下c的取值是各自独立排序的,不再全局有序 -
c = 'x'就无法再用索引二分查找,只能对每个匹配的b值逐个扫描其下的c,等价于放弃索引
所以 key_len 显示只用了前两列长度,EXPLAIN 中 key 列有值但 Extra 出现 Using where,就是典型信号。
如何调整字段顺序让范围列“靠右”
核心原则:把等值过滤字段放前面,范围字段尽量靠后,且只保留一个(或最多一个主导范围)。
- 如果查询固定是
WHERE status = ? AND create_time BETWEEN ? AND ?,索引应建为idx_status_time,而非idx_time_status - 若还有第三个字段常用于等值过滤(如
user_id),优先级顺序应是:user_id(等值)→status(等值)→create_time(范围) - 避免在同一个复合索引里塞多个范围列,例如
(a, b, c)中a > 1 AND b > 2 AND c = 3,只有a能走索引
示例:原索引 idx_age_classId_name,查询 WHERE age = 30 AND classId > 20 AND name = 'abc' → name 不生效;改为 idx_age_name_classId 后,WHERE age = 30 AND name = 'abc' AND classId > 20 就能全命中。
IN 是范围吗?它和 = 有什么区别
IN 在 MySQL 中属于“等值集合”,但优化器是否将其当作范围处理,取决于值的数量和分布:
- 单值
IN (1)等价于= 1,不触发右侧失效 - 多值
IN (1,2,3)通常仍视为等值匹配,右侧列可继续生效(前提是这些值在索引中物理连续) - 但如果
IN值过多(如几百个)、或对应数据在索引页中高度离散,优化器可能退化为全索引扫描,此时也相当于“范围逻辑” - 对比
OR:多个OR条件(如a=1 OR a=2)在无合并优化时更易导致索引失效,而IN是语法层优化过的等价写法
验证方式始终是 EXPLAIN:看 key_len 是否覆盖全部索引列,以及 rows 是否明显放大。
容易被忽略的边界情况
即使字段顺序调对了,以下细节仍会导致右侧列“看似生效实则无效”:
-
LIKE 'xxx%'属于范围操作(前缀匹配),但它**不会**导致右侧列失效 —— 这是唯一例外,因 B+ 树天然支持前缀有序遍历 -
IS NULL或IS NOT NULL在某些 MySQL 版本中被当作范围条件,尤其当字段允许 NULL 时,需实测EXPLAIN - 隐式类型转换会悄悄把等值变成范围:比如
WHERE int_col = '123'触发转换,int_col后面的索引列也会失效 - 使用
ORDER BY时若顺序与索引不一致,可能导致优化器放弃使用该索引做排序,转而用文件排序,间接削弱范围列收益
真正关键的不是“有没有范围”,而是“范围出现在索引定义里的第几位”——这个位置决定了你能压榨出多少索引能力。











