在这里插入图片描述

用MySQL写查询语句的时候,经常遇到一种情况:明明给字段建了索引,可执行计划里就是不走索引,查了半天发现是没搞懂最左前缀匹配原则。这个原则看着有点抽象,其实实际用起来很简单,踩过几次坑后我就摸透了门道。今天就用大白话结合代码示例,把这个知识点讲清楚,希望能帮到和我当初一样困惑的朋友。

先搞懂:最左前缀匹配原则到底是啥?

我认为最左前缀匹配原则的核心意思特别直白——MySQL在使用联合索引(多字段组合的索引)时,会从索引的最左边第一个字段开始匹配,依次往后,只要中间有一个字段不满足匹配条件,后面的字段就没法再用索引了。

打个比方,联合索引就像我们查字典时用的拼音索引,比如“zhang san”,字典会先按“z”找,再找“ha”,接着“ng”,一步步匹配。如果跳过“z”直接找“san”,字典的拼音索引就用不上了,只能逐页翻找。MySQL的联合索引也是这个道理,必须从最左边的字段开始匹配,不能跳过前面的字段。

先给大家建个测试表,后面的例子都基于这个表:

-- 创建用户表,建联合索引 (username, age, address)
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `age` int(11) DEFAULT NULL,
  `address` varchar(100) DEFAULT NULL,
  `phone` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`),
  -- 联合索引:username 是最左前缀,接着是 age,最后是 address
  KEY `idx_username_age_address` (`username`, `age`, `address`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

这里建的联合索引是 (username, age, address),最左前缀就是 username,然后是 username+age,再到完整的三个字段组合。

哪些情况能命中索引?哪些不能?

这是最实用的部分,我整理了几种常见的查询场景,结合例子给大家分析,一看就明白。

场景1:匹配最左前缀字段(完全命中索引)

如果查询条件里包含联合索引最左边的字段,不管是只包含第一个,还是包含前两个、全部三个,都能命中索引(只是命中的索引长度不一样)。

-- 1. 只查最左前缀 username:命中索引(用到 idx_username_age_address 的 username 部分)
SELECT * FROM `user` WHERE username = '张三';

-- 2. 查 username + age:命中索引(用到 username 和 age 部分)
SELECT * FROM `user` WHERE username = '张三' AND age = 25;

-- 3. 查 username + age + address:完全命中索引(三个字段都用到)
SELECT * FROM `user` WHERE username = '张三' AND age = 25 AND address = '北京市';

这三种情况都能正常走索引,因为都是从最左前缀 username 开始匹配的,符合最左前缀原则。

场景2:跳过最左前缀字段(完全不命中索引)

如果查询条件里跳过了最左边的字段,直接查后面的字段,索引就完全用不上了,MySQL会走全表扫描。

-- 1. 跳过 username,直接查 age:不命中索引(全表扫描)
SELECT * FROM `user` WHERE age = 25;

-- 2. 跳过 username,查 age + address:不命中索引(全表扫描)
SELECT * FROM `user` WHERE age = 25 AND address = '北京市';

-- 3. 只查 address:不命中索引(全表扫描)
SELECT * FROM `user` WHERE address = '北京市';

这几种情况都是典型的违反最左前缀原则的例子,联合索引里最左边的 username 没用到,后面的字段再怎么组合也没用,索引相当于白建了。

场景3:查询条件顺序不影响(MySQL会优化)

很多人会误以为“查询条件的顺序必须和联合索引的字段顺序一致”,其实不是这样的。MySQL的查询优化器会自动调整条件的顺序,只要包含最左前缀字段,不管条件写在前面还是后面,都能命中索引。

-- 条件顺序是 age + username,和索引字段顺序不一致,但能命中索引
SELECT * FROM `user` WHERE age = 25 AND username = '张三';

-- 条件顺序是 address + age + username,同样能命中索引
SELECT * FROM `user` WHERE address = '北京市' AND age = 25 AND username = '张三';

这两个查询都能命中索引,因为优化器会自动把 username 调到最前面去匹配索引。但我建议还是按索引字段顺序写查询条件,这样代码可读性更高,也不用依赖优化器的调整。

场景4:范围查询后的字段无法命中索引

如果最左前缀字段后面跟了范围查询(比如 >, <, BETWEEN, LIKE '%xxx'),那么范围查询后面的字段就没法再用索引了,只能用到范围查询之前的字段。

-- 1. username 等值查询,age 范围查询:只能用到 username 和 age 部分,address 用不上
SELECT * FROM `user` WHERE username = '张三' AND age > 25 AND address = '北京市';

这个查询里,username 是等值匹配,age 是范围查询,所以索引只能用到 usernameage,后面的 address 虽然在查询条件里,但没法命中索引,会被忽略。

再看一个例子:

-- 2. username 用 LIKE 范围查询(%在后面,属于前缀匹配,还能用到 username 索引)
-- age 和 address 用不上索引
SELECT * FROM `user` WHERE username LIKE '张%' AND age = 25 AND address = '北京市';

这里 username LIKE '张%' 是前缀匹配,属于范围查询的一种,能用到 username 部分的索引,但后面的 ageaddress 就没法用了。如果是 username LIKE '%三'(%在前面),那 username 的索引也用不上了,会全表扫描。

场景5:包含非索引字段(不影响索引命中)

如果查询条件里除了联合索引的字段,还包含其他非索引字段(比如例子里的 phone),只要最左前缀字段存在,依然能命中索引。

-- 包含非索引字段 phone,但有 username 和 age,依然命中索引
SELECT * FROM `user` WHERE username = '张三' AND age = 25 AND phone = '13800138000';

这里 phone 不是索引字段,但不影响 usernameage 命中索引,MySQL会先通过索引找到符合条件的记录,再过滤 phone 字段的条件。

场景6:包含非索引字段(不影响索引命中)

这种场景是联合索引使用中很常见的情况,比如联合索引为 (username, age, address),查询条件包含最左前缀 username 和后面的 address,但跳过了中间的 age 字段。

SELECT * FROM user WHERE username = '张三' AND address = '北京市' 

以这个例子为例,这里的索引命中情况要拆成检索阶段和过滤阶段来看:

检索阶段:只有 username 能命中索引,因为最左前缀匹配原则要求字段从左到右依次匹配,跳过 age 后,address 无法参与索引检索。MySQL 会先通过 username = ‘张三’ 在索引中定位到所有相关条目,这一步是走索引的。

过滤阶段:address 的过滤是否用到索引,取决于是否是覆盖索引场景:

  • 非覆盖索引(如 SELECT *):需要根据索引条目里的主键回表查询完整数据,address = ‘北京市’ 的过滤在回表后完成,没用到索引优势;
  • 覆盖索引(如 SELECT username, address):查询字段都在联合索引里,address 的过滤直接在索引条目中完成,不用回表,性能依然很高。

用 EXPLAIN 验证时,非覆盖索引场景的 key_len 只会显示 username 字段的长度,Extra 列显示 Using where; Using index condition;覆盖索引场景的 Extra 列会显示 Using where; Using index,说明 address 在索引里完成了过滤。

我认为这种场景最容易让人误解 “是否命中索引”,核心是要区分 “检索” 和 “过滤” 的差异。如果业务中经常需要用 username + address 查询,建议调整联合索引字段顺序为 (username, address, age),让两个字段都能参与索引检索。

我踩过的坑:这些误区要避开

在实际开发中,我遇到过几个容易误解的点,分享给大家,避免再踩坑。

误区1:以为“覆盖索引”能绕过最左前缀原则

覆盖索引是指查询的字段刚好是索引的一部分,不用回表查数据,但它依然要遵守最左前缀原则。比如:

-- 想通过覆盖索引查 age,但跳过了 username:依然不命中索引
SELECT age FROM `user` WHERE age = 25;

虽然查询的 age 是索引字段,但因为跳过了最左前缀 username,还是没法命中索引,只能全表扫描。

误区2:联合索引的字段顺序随便定

很多人建联合索引时,随便把字段拼在一起,其实字段顺序很关键。我认为应该把“查询频率高、区分度高”的字段放在最左边,这样才能最大化利用最左前缀原则。

比如上面的例子,username 是查询频率最高的(很多查询都会按用户名查),所以放在最左边;如果你的业务里经常按 age 查,那应该把 age 放在联合索引的最左边,建索引 (age, username, address)

误区3:用函数或表达式处理最左前缀字段

如果对最左前缀字段用了函数或表达式处理,索引会失效,最左前缀原则也没法生效。

-- 对 username 用函数处理,索引失效(全表扫描)
SELECT * FROM `user` WHERE UPPER(username) = 'ZHANGSAN';

-- 对 username 做字符串拼接,索引失效
SELECT * FROM `user` WHERE CONCAT(username, '123') = '张三123';

这种情况要避免,尽量不对索引字段做函数或表达式处理,如果必须处理,可以考虑用生成列(Generated Column)建索引。

我们的经验:怎么合理利用最左前缀原则?

结合项目中的实际使用经验,我总结了几个实用建议:

  1. 建联合索引时,按“查询频率从高到低”排列字段:把最常作为查询条件的字段放在最左边,这样能最大化命中索引的概率。比如经常用 username 查,偶尔用 username+age 查,就建 (username, age, address);如果经常用 age 查,就把 age 放最左边。

  2. 避免跳过最左前缀查询:如果业务上确实需要经常按 ageaddress 单独查询,要么调整联合索引的字段顺序,要么给这些字段单独建索引(比如给 age 建一个单列索引),不要硬用联合索引。

  3. 范围查询尽量放在后面:如果查询条件里有范围查询(比如 age > 25),尽量把它放在联合索引的后面字段,这样前面的字段能完全命中索引,后面的字段虽然用不上,但也能减少扫描范围。

  4. 用EXPLAIN验证索引是否命中:不确定查询是否走索引时,用 EXPLAIN 命令看一下执行计划,如果 type 列是 refrangekey 列显示对应的联合索引名称,就说明命中了;如果 typeALLkeyNULL,就是全表扫描,没命中索引。

示例:

-- 用 EXPLAIN 验证索引是否命中
EXPLAIN SELECT * FROM `user` WHERE username = '张三' AND age = 25;

执行后看 key 列,如果显示 idx_username_age_address,就说明命中了联合索引。

最后想说

最左前缀匹配原则其实就是联合索引的“使用规则”,核心就是“从左到右,不能跳过”。我认为只要记住“最左前缀是关键,跳过前面的字段就没用”,再结合实际场景多测试,就能轻松掌握。

建联合索引的时候,一定要结合自己的业务查询场景来设计字段顺序,不能盲目拼接字段,否则索引建了也用不上,还会占用存储空间、影响增删改的性能。如果不确定索引是否合理,多用水晶球 EXPLAIN 分析执行计划,慢慢就能找到最优的索引设计方案。

如果有哪里理解不到位的地方,欢迎大家指正呀!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐