MySQL联合索引必须从最左列开始匹配,本质是B+树按a主序、b次序、c末序构建的单棵树结构决定的:a全局有序可二分查找,b仅在a固定时局部有序,c仅在a和b都固定时局部有序;跳过a则无法定位起点,故WHERE b=2等查询无法使用索引。

MySQL联合索引必须从最左列开始匹配,不是设计偏好,而是B+Tree索引结构的物理限制——它只能按一个顺序组织数据,这个顺序由联合索引定义时最左边的字段决定。
联合索引在B+Tree中实际怎么存
联合索引 (a, b, c) 不是三棵树,而是一棵以 a 为主序、b 为次序、c 为末序的单棵B+Tree。叶子节点里,数据按 a 升序排列;a 相等时,再按 b 升序;a 和 b 都相等时,才按 c 升序。
这意味着:
-
b字段单独看是无序的——无法直接二分查找b = 5 -
c字段更无全局顺序——只有在确定了a和b的具体值后,c才形成一段局部有序区间 - 优化器可以重排
WHERE条件顺序(如写成WHERE b=2 AND a=1),但底层仍要先定位a=1这个主分支,再在其中筛b=2
为什么 WHERE b=2 无法走联合索引 (a,b,c)
因为B+Tree根节点和非叶节点只存 a 的分割值(比如:a ≤ 10 → 左子树,a > 10 → 右子树)。当查询不带 a 时,引擎根本不知道该从哪条路径往下找——没有起点,整棵树就“看不见”。
常见错误现象:
-
EXPLAIN结果中possible_keys为空,key为NULL - 执行计划类型(
type)退化为ALL或index,而非ref/range - 即使表很小,
WHERE b=2也会触发全表扫描,而非索引扫描
范围查询为什么让右边字段失效
假设索引是 (a, b, c),执行 WHERE a = 1 AND b > 10 AND c = 5:
-
a = 1定位到所有a=1的叶子页范围 -
b > 10在该范围内线性扫描满足条件的记录(因为b在a=1下虽有序,但范围查询无法精确定界后续c的位置) -
c = 5此时已失去索引加速能力——引擎只能对上一步筛选出的每条记录逐个判断c值,即Using where
关键点在于:b > 10 是一个“开区间”,它打破了 c 的局部有序性。B+Tree无法在不确定 b 具体值的情况下,跳转到某个 c=5 的精确位置。
哪些写法看似合理,实则踩坑
以下写法在语法上合法,但会意外丢失索引能力:
- 隐式类型转换:
WHERE a = '1'(a是INT)→ 触发全索引扫描,key_len异常变短 - 对索引列使用函数:
WHERE UPPER(b) = 'X'→b无法直接比对,索引失效 -
LIKE左模糊:WHERE b LIKE '%abc'→ 无法利用b的有序性,退化为全量扫描 - 使用
IS NULL或!=:WHERE a != 1→ 通常无法用索引做高效定位(除非是覆盖索引 + 索引下推)
真正容易被忽略的是:即使 WHERE 条件包含全部三列,只要其中任意一列用了范围查询,它右边所有列就自动“掉线”——这个截断是静默发生的,EXPLAIN 不会额外警告,只看 key_len 和 Extra 才能确认是否用全了索引。











