联合索引的b+树按字段顺序严格字典排序,范围查询(如>、>=、

联合索引的B+树排序逻辑决定了范围即断点
联合索引 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' 做索引内定位,只能回表后逐行过滤。
这不是 MySQL 的 bug,也不是配置问题,而是 B+ 树遍历能力的数学边界——范围查询天然破坏了“单一分组内有序”这个前提。
EXPLAIN 中 key_len 和 Extra 是判断失效的直接证据
仅看 key 字段是否命中索引是远远不够的;真正关键的是 key_len 和 Extra:
-
key_len显示实际用到的索引字节数。例如idx(a, b, c)中各字段长度分别为 4、2、1,若key_len = 6,说明只用了a和b,c没参与索引查找 -
Extra出现Using where(而非Using index)时,代表对应条件是在引擎层回表后做的过滤,不是索引下推 -
type为range但rows异常高,往往意味着右侧列已失效,导致扫描行数远超预期
执行 EXPLAIN FORMAT=TRADITIONAL SELECT * FROM t WHERE a > 1 AND b = 2;,如果 key_len 只覆盖 a 字段长度,且 Extra 含 Using where,就能确认 b = 2 没走索引。
哪些操作算“范围”,哪些不算?别被 = 和 IN 带偏
是否触发右侧列失效,取决于是否具备“单点定位能力”,而不是表面看起来像不像等值:
- 明确算范围(右侧列全部失效):
>、>=、、<code>、<code>BETWEEN、LIKE 'abc%' - 明确不算范围(右侧列仍有效):
=、IN (1,2,3)、IS NULL——IN本质是多个等值查找,每条路径上后续列依然有序 - 容易误判但实际也失效:
!=、NOT IN、IS NOT NULL,它们通常等价于全范围扫描,同样截断右侧 -
LIKE '%abc'或LIKE '%abc%':该列本身就不走索引,不涉及“右侧失效”问题,因为连最左都没进去
特别注意:a >= 10 和 a > 9 在索引行为上完全等价,都属于范围;不要以为带等号就更“安全”。
真正有效的解法只有两个方向,没有第三条路
强制加 FORCE INDEX、调整 WHERE 条件顺序、用 UNION ALL 拆查询……这些都不能让被范围截断的列重新获得有序性。唯一靠谱的路径只有:
- 把大概率用于等值过滤的高选择性列前置,比如状态码、租户ID、分类ID;把易范围查询的列(如时间、金额、年龄)尽量靠右。例如把
idx(created_at, status)改成idx(status, created_at) - 如果业务中存在跳过中间列的查询(如
WHERE a = ? AND c = ?),不要强求一个三列联合索引覆盖全部,宁可建两个窄索引:idx(a, c)和idx(b, c) - 覆盖索引(
SELECT a,b FROM t WHERE a = 1 AND b > 2,且索引包含a,b)能减少回表,但它不能让c = 5这种被截断的条件重新走索引
最常被忽略的一点是:范围查询的位置比字段本身的选择性更重要。哪怕 b 的区分度远高于 a,只要 a 在联合索引里靠左且被范围查询,b 就注定失效。











