range查询导致联合索引右侧字段失效,是因为b+树按字段顺序逐层排序,范围操作(如>、

Range查询为什么让联合索引右侧字段失效
因为MySQL的B+树索引是按字段顺序逐层排序的,一旦遇到范围查询(如 >、>=、、<code>、<code>BETWEEN、LIKE 'prefix%'),索引扫描就只能“停在这一层”,无法再向下精确跳转到右侧字段的有序结构中。这不是bug,而是B+树天然的遍历限制。
例如索引为 (a, b, c),执行 WHERE a = 1 AND b > 10 AND c = 5:
a = 1 可精确定位到某段子树;
b > 10 需要遍历该子树中所有 b > 10 的叶子节点;
但这些节点里 c 的值是杂乱无序的(因只按 a,b 排序),所以 c = 5 只能靠逐行过滤,无法用索引加速 —— 这就是“右侧失效”。
哪些操作算Range,哪些不算
关键看是否破坏了“单点定位能力”:
-
=、IN、IS NULL:支持最左前缀连续匹配,右侧字段仍有效 -
>、>=、、<code>、<code>BETWEEN、LIKE 'abc%':触发Range,右侧字段索引失效 -
!=、、NOT IN、IS NOT NULL:通常等价于全范围扫描,也会截断右侧 -
LIKE '%abc'或LIKE '%abc%':不走索引,自然谈不上右侧失效
注意:IN 是特例 —— 它本质是多个 = 的合并,只要左侧字段全是等值,右侧仍可命中。比如 WHERE a IN (1,2) AND b = 3 在 (a,b) 索引上完全生效。
重构联合索引时如何安排字段顺序
目标不是“把高选择性字段放最左”就完事,而是要让**最可能被范围查询的字段尽量靠右**,给左侧留出稳定过滤空间:
- 把必然用于等值过滤的字段(如状态码、租户ID、类型标识)放在最左
- 把大概率参与范围查询的字段(如时间、金额、评分)放在中间或偏右
- 把仅用于排序或覆盖查询的字段(如需要
SELECT的列)放在最右 —— 它们本就不承担过滤职责 - 如果某字段既常被等值查、又偶尔被范围查,优先按等值场景排;若范围查询频次更高,就把它右移,同时确认左侧字段能否单独支撑大部分查询
反例:CREATE INDEX idx_time_status ON orders (created_at, status) —— created_at 极易范围查询,一用就废掉 status。应改为 (status, created_at),先筛状态,再对结果集按时间范围切片。
验证右侧是否真失效,别只看EXPLAIN
EXPLAIN 的 key_len 值才是铁证:它显示MySQL实际用了索引的前多少字节。对照索引定义算出各字段理论长度(注意字符集、是否允许NULL、是否为前缀索引),就能判断用到了第几个字段。
例如索引 (a INT, b VARCHAR(50), c DATETIME),假设 a 占4字节、b 实际用20字节、c 占5字节:
-
key_len = 4→ 只用了a -
key_len = 24→ 用了a + b(4 + 20) -
key_len = 29→ 三个字段全用上了
如果 WHERE a = 1 AND b > 'x' AND c = '2025-01-01' 下 key_len = 24,说明 c 没进索引查找,只是回表后过滤 —— 这才是右侧失效的确凿信号。











