你建了 (a, b, c) 联合索引,结果按 b 单独查询还是全表扫描,EXPLAIN 里 type 一直是 ALL——这种"索引建了跟没建"的情况,九成是踩了最左前缀原则。一句话结论:联合索引必须从最左边的列开始、且连续使用,才能完整命中;一旦跳过左侧列,或在中间列用了范围查询,右侧列就再也用不上索引了。

最左前缀不是"SQL 里写的顺序",而是"索引列定义的顺序"。很多人把 WHERE b = 1 AND a = 2 误以为用不上 (a,b) 索引,其实 MySQL 优化器会重排条件,照样能命中。真正让它失效的是"跳过 a"或"在 a 之后用了范围"。这篇文章,编程狮就把最左前缀从原理、命中、失效到排查一步步讲清楚,并配上 EXPLAIN 验证方法,照着做就能定位自己的慢查询。
一、先看结论:怎么算命中
把联合索引想成"电话簿按 姓→名→电话 排序"。你只凭"名"去找,簿子本身不是按名排的,只能逐页翻——这就是跳过最左列的后果。只有从"姓"开始查,才能利用已有的有序性二分定位。
命中规则可以浓缩成三句话:第一,查询条件必须从索引的第一列开始;第二,中间的列要连续出现,不能断;第三,一旦某一列用了范围(<、>、BETWEEN、非前缀的 LIKE),它右边的列就退化为普通过滤,不再走索引有序性。适用场景上,等值查询命中率最高,范围查询会截断右侧。如果业务里经常出现"只查后面几列"的需求,更该反思索引列的顺序是否和查询形态匹配,而不是盲目加单列索引。
最左前缀的本质,是 B+ 树索引按"列定义的先后顺序"把数据排成有序结构。索引 (a, b, c) 物理上等价于先按 a 排、a 相同再按 b 排、b 相同再按 c 排。所以只有"前缀连续"的查询条件,才能沿着这棵有序树一路二分下去;一旦前缀断掉,后面的列在树里就不再相邻,优化器只能退化为逐行扫描。
二、哪些写法能命中,哪些会失效
能完整命中的写法:WHERE a = 1(只用最左列)、WHERE a = 1 AND b = 2、WHERE a = 1 AND b = 2 AND c = 3、WHERE a = 1 AND b = 2 AND c > 3(c 用范围,但 a、b 等值,c 仍可走范围扫描)。
会失效或退化的写法:只查 b = 2 或 c = 3,跳过 a,索引完全用不上;查 a = 1 AND c = 3,跳过了 b,只有 a 能走索引,c 退化成回表过滤;a > 1 AND b = 2,a 是范围,b 用不上。基础语法见 MySQL 教程。
这里有个容易混淆的点:a = 1 AND b > 2 AND c = 3,a 等值命中,b 范围扫描,c 由于 b 已是范围,无法再走有序索引,只能回表逐行比对。不要想当然以为 c 也会用到。反过来,如果查询是 a > 1 AND b = 2,a 是范围导致 b 也用不上索引,这时候把 b 放前面的 (b, a) 索引反而更合适——索引列顺序要跟着"等值在前、范围在后"的原则排。
三、范围查询为什么会"截断"索引
来看一个具体例子,索引是 (a, b, c):
-- 能用到 a、b 的有序性,c 在 b 范围之后只能回表过滤
SELECT * FROM t WHERE a = 1 AND b > 10 AND c = 3;
-- EXPLAIN 中 key_len 只覆盖到 b,ref 显示用到 a、b
-- c = 3 出现在 Extra 的 Using where,说明是回表后过滤
这里 b > 10 是范围,所以 c = 3 无法利用 (a,b,c) 索引的有序性,只能回表过滤。若业务常按 a 等值 + c 等值查,而 b 多是范围,应考虑把等值列放前面,或建 (a, c, b) 这类更符合查询形态的索引。数据迁移时索引策略可参考 Mysql Migration 教程。
性能对比:等值链越长,索引过滤越彻底,回表行数越少,扫描行数呈数量级下降;一旦右侧退化,回表行数可能暴涨,查询从毫秒级退化到秒级。这也是最左前缀原则在大数据量表上影响巨大的原因。范围查询之后之所以截断,本质上是因为 B+ 树的有序性只能保证"前缀连续"的那一段可以被高效定位,一旦某一列变成"范围区间",后面的列在树里就不再连续有序,优化器只能退而求其次,把后面的列拿去回表过滤。
四、三个最容易踩的坑
第一个坑:跳过最左列。比如索引 (city, age),却只按 age 查,type 直接是 ALL。修复就是补上最左列条件,或单独为 age 建索引。
第二个坑:对列做函数运算或隐式类型转换,索引直接失效,例如 WHERE DATE(create_time) = ... 或字符串列用数字比较。写成范围/等值裸列才走索引。第三个坑:用了 !=、<>、OR 连非索引列,优化器往往放弃索引。排查可对照 MySQL 速查手册 的执行计划章节。
-- 错误:对列套函数,索引失效
SELECT * FROM t WHERE DATE(create_time) = '2026-10-28';
-- 正确:范围裸列,可走索引
SELECT * FROM t WHERE create_time >= '2026-10-28' AND create_time < '2026-10-29';
第四个坑常被忽略:索引列的顺序和查询里"最常用等值列"不匹配。如果发现某张表总在按 (b, c) 查、a 很少用,那 (a, b, c) 就不如 (b, c, a) 实用。建索引前先统计高频查询形态,再决定列顺序,比事后加索引更省事。另外,索引不是越多越好——每个索引都会拖慢写入,联合索引能覆盖的查询就别再单列重复建。举个具体例子:一张订单表常按 user_id 等值 + created_at 范围查,却不按 status 查,那索引就该是 (user_id, created_at, status) 而非把 status 放最前;反过来如果 status 才是高频等值条件,顺序就要调整。索引设计没有公式,只有"贴合你自己查询形态"这一条准绳。
五、用 EXPLAIN 验证索引是否生效
验证命令很简单,在查询前加 EXPLAIN:
EXPLAIN SELECT * FROM t WHERE a = 1 AND b = 2 AND c = 3;
重点看三列:type 至少到 ref 或 range 才算用上索引,ALL 就是全表扫描;key 显示实际使用的索引名;key_len 越长说明用到的索引列越多。Extra 里出现 Using index condition 或 Using where 是正常的,但若出现 Using filesort 就要警惕排序没走索引。索引与执行计划在 MySQL8 教程 里有更系统的讲解。实战中还有一个细节:如果 EXPLAIN 显示 key 不是你期望的索引,先用 ANALYZE TABLE 更新统计信息,再让优化器重新选择执行计划;陈旧的统计信息常让优化器误判而放弃索引。对于分区表或视图,索引命中的判断还要叠加存储引擎特性,不能完全套用上面的通用规则,必要时在测试库用真实数据量复现一次最稳妥。
如果 EXPLAIN 仍显示全表扫描,优先检查是否对列做了函数运算、是否隐式转换、是否跳过最左列。可结合 SQL Server 速查手册 对比不同库的索引行为差异。MySQL 和 SQL Server 在联合索引上的逻辑一致,但执行计划展示字段略有不同,对照看能更快建立直觉。遇到 key 为空但你觉得"明明有索引"时,多半是上面三类失效写法之一,逐条对照即可定位。


六、覆盖索引:少回表也能提速
当查询的所有列都包含在索引里,MySQL 可以只扫索引不回表,这叫"覆盖索引",Extra 里会显示 Using index。比如索引 (a, b, c),查询 SELECT a, b FROM t WHERE a = 1 AND b = 2,列全在索引内,速度最快。设计联合索引时,如果常查的列不多,可以把它们一起放进索引末尾,用空间换回表开销。但要注意,覆盖索引会让索引更宽、写入更慢,只在高频读、少写入的表上划算。
总结
联合索引必须从最左列开始、连续使用才能完整命中;跳过最左列、或在中间列使用范围(<、>、BETWEEN)都会让右侧列退化成回表过滤。排查时一律用 EXPLAIN 看 type、key、key_len,优先消除"对列套函数""隐式类型转换""跳过最左列"这三类失效写法。等值链越长、回表越少,查询越快;索引列顺序遵循"等值在前、范围在后",必要时用覆盖索引进一步少回表。
常见问题
Q:WHERE b=2 AND a=1 会不会用不上 (a,b) 索引?
不会。MySQL 优化器会自动重排 AND 条件的顺序,只要查询里同时包含 a、b 的等值条件,就能命中 (a,b) 索引。顺序失效的说法是个常见误解。
Q:范围查询后面真的完全不能用索引吗?
不是完全不能用,而是右侧列无法利用索引的"有序性"做快速定位,只能回表后逐行过滤。左侧等值列仍然正常命中,只是整体效率比纯等值链低。
Q:怎么确认我的查询到底走没走索引?
在查询前加 EXPLAIN,看 type 是否为 ref/range(而非 ALL),key 是否显示了你的联合索引名,key_len 长度是否符合预期用到的列数。

TRAE-AI编程



