MySQL 联合索引范围查询:最左匹配原则的一个学习笔记

前情提要

最近看到一篇关于 MySQL 联合索引的文章,里面纠正了一个很常见的说法:

联合索引的最左匹配原则,会一直向右匹配,直到遇到范围查询(><betweenlike)就停止。

这句话在很多资料里都能看到,我以前也会把它当成一个比较简单的记忆结论。但仔细看完之后发现,它其实不够准确。

更准确的理解应该是:

联合索引在遇到某些范围查询时,后续字段可能无法继续参与构造索引扫描区间;但并不是所有范围查询都会让后续字段完全失效。像 >=<=BETWEENLIKE 'xxx%' 这类场景,在 MySQL 中仍然可能继续利用联合索引中的后续字段。

这篇文章就按学习笔记的方式,把这个问题重新梳理一下。

先回顾一下 B+Tree 索引

InnoDB 中常见的索引底层结构是 B+Tree。

对于主键索引来说,叶子节点存放的是完整的行数据,所以主键索引也叫聚簇索引。

对于二级索引来说,叶子节点存放的是索引列的值以及对应的主键值。如果通过二级索引查到了主键,再回到主键索引中查完整行数据,这个过程就是回表。

比如:

1
select * from product where product_no = '0002';

如果 product_no 上有二级索引,查询会先在 product_no 的索引树上找到对应记录,再拿到主键值,最后回表查询完整数据。

如果查询字段本身已经能从二级索引中拿到,就不需要回表,这就是覆盖索引。

1
select id from product where product_no = '0002';

理解这些,是为了后面理解联合索引的扫描过程。

联合索引是怎么排序的?

假设有一个联合索引:

1
create index idx_a_b_c on t_table(a, b, c);

它不是分别给 abc 各建了一棵树,而是按照 (a, b, c) 这个组合顺序构建一棵 B+Tree。

排序规则可以简单理解为:

  1. 先按 a 排序;
  2. a 相同的情况下,再按 b 排序;
  3. ab 都相同的情况下,再按 c 排序。

所以,对于 (a, b, c) 这个联合索引:

1
2
3
where a = 1
where a = 1 and b = 2
where a = 1 and b = 2 and c = 3

这些条件都符合最左匹配原则。

但是:

1
2
3
where b = 2
where c = 3
where b = 2 and c = 3

这些条件无法从联合索引最左边开始匹配,因此不能有效利用这个联合索引做快速定位。

原因也很简单:在整棵联合索引树中,a 是全局有序的;但如果脱离了 abc 并不是全局有序的,只是在前置字段相同的局部范围内有序。

范围查询为什么会影响后续字段?

问题的关键在于:一个查询条件能不能帮助 MySQL 缩小索引扫描区间。

比如联合索引是 (a, b),查询语句是:

1
select * from t_table where a > 1 and b = 2;

由于索引先按 a 排序,所以 a > 1 可以帮助 MySQL 找到一个扫描范围。

但是在 a > 1 的这个范围里,b 并不是全局有序的。因为这些记录可能来自很多不同的 a 值,每个 a 值下面的 b 才是局部有序。

因此,b = 2 很难继续用于缩小 B+Tree 的扫描边界。它更像是在扫描过程中再做过滤。

这就是常说的:遇到 >< 这样的范围查询后,后面的字段无法继续参与索引定位。

>=BETWEEN 为什么不一样?

再看一个类似的查询:

1
select * from t_table where a >= 1 and b = 2;

它看起来也是范围查询,但和 a > 1 有一个细节差异:a >= 1 包含了 a = 1 这个边界值。

a = 1 的局部范围内,b 是有序的。

所以 MySQL 在构造扫描区间时,仍然有机会结合 b = 2 来定位边界,而不是完全忽略 b

BETWEEN 在 MySQL 中也是类似的,因为 MySQL 的 BETWEEN value1 AND value2 包含左右边界,语义接近:

1
a >= value1 and a <= value2

所以类似下面的查询:

1
select * from t_table where a between 2 and 8 and b = 2;

在 MySQL 中,b 也可能继续参与联合索引的扫描区间构造。

这也是“范围查询一定让后续字段失效”这个说法不严谨的地方。

LIKE 'j%' 也不一定会停止匹配

再看一个字符串前缀匹配的例子。

假设有联合索引 (name, age)

1
select * from t_user where name like 'j%' and age = 22;

LIKE 'j%' 是前缀匹配,它可以利用索引,因为所有以 j 开头的 name 在索引中是相邻的。

它大致可以形成一个类似这样的扫描区间:

1
['j', 'k')

也就是从 j 开头的位置开始扫,到 k 之前停止。

同时,在某些边界或局部相同值的范围内,后续字段 age 仍然可能参与索引条件判断。所以 LIKE '前缀%'LIKE '%关键字%' 是完全不同的情况。

前者可能用到索引,后者因为前面有通配符,通常无法利用索引做有序定位。

怎么判断到底用了几个字段?

学习这个问题时,不要只看执行计划里的 key

因为 key = idx_a_b 只能说明这条 SQL 使用了这个联合索引,但不能说明联合索引里的所有字段都参与了索引定位。

更应该关注的是 key_len

key_len 可以帮助我们判断优化器使用了多少索引字段来构造扫描区间。

比如字段 ab 都是 int not null

  • 如果 key_len = 4,通常说明只用到了一个 int 字段;
  • 如果 key_len = 8,通常说明两个 int 字段都参与了索引条件。

当然,实际计算时还要考虑字段是否允许 NULL、是否为变长字段、字符集等因素。

比如 varchar(30)utf8mb4 下最多占用 30 * 4 = 120 字节,变长字段还需要额外字节记录长度,所以 key_len 会比直觉上更大。

这次学习后的结论

我觉得这篇文章最有价值的地方,不只是纠正了一个结论,而是提醒我们:

MySQL 索引优化不能只背口诀,要回到 B+Tree 的有序性和扫描区间去理解。

可以把结论整理成下面这样:

  1. 联合索引遵循最左匹配原则;
  2. 联合索引的字段顺序决定了 B+Tree 的排序规则;
  3. 后续字段能不能继续用上索引,取决于它能不能参与缩小扫描区间;
  4. >< 这类范围查询后,后续字段通常不能继续参与索引定位;
  5. >=<=BETWEENLIKE 'xxx%' 在 MySQL 中并不一定会让后续字段停止匹配;
  6. 判断联合索引具体用了几个字段,不要只看 key,还要结合 key_len
  7. 最好通过 EXPLAIN 验证,而不是只背固定结论。

最后

以后再看到“联合索引遇到范围查询就停止匹配”这句话,我会更谨慎一点。

它可以作为一个粗略记忆,但不能当成完整结论。

真正理解这个问题,还是要回到两个点:

  • 联合索引中的字段是如何排序的;
  • 查询条件是如何形成索引扫描区间的。

这比单纯背一句八股文更可靠。


MySQL 联合索引范围查询:最左匹配原则的一个学习笔记
https://seeyourface.cn/2026/06/04/MySQL 联合索引范围查询:最左匹配原则的一个学习笔记/
作者
Yang Lei
发布于
2026年6月4日
许可协议