MySQL 索引失效别再背八股:看懂 EXPLAIN,面试官追问也不慌

最近后端面试里,MySQL 索引这块又开始高频起来。

我看过不少候选人的回答,最大的问题不是背得少,而是背成了口诀:函数会失效、范围后面会失效、like '%xx' 会失效、隐式转换会失效。面试官继续追一句:"那这条 SQL 到底用了索引没有?用了哪一段?为什么 Extra 里还有 Using index condition?"很多人就卡住。

索引失效这个说法本来就有点粗。

更准确的说法是:优化器还能不能利用索引的有序性做定位?还能不能用索引里的列提前过滤?最后需不需要回表?

把这三个问题分开,面试里就不容易被追着打。

先别谈失效,先看联合索引到底按什么排序

假设有一张用户表:

sql 复制代码
CREATE TABLE user_profile (
  id BIGINT PRIMARY KEY,
  age INT NOT NULL,
  city VARCHAR(32) NOT NULL,
  score INT NOT NULL,
  name VARCHAR(64) NOT NULL,
  INDEX idx_age_city_score (age, city, score)
);

idx_age_city_score 不是三棵树,也不是三个索引拼在一起。

MySQL 官方手册对多列索引的描述很直白:多列索引可以看成一个按多个列值拼接后排序的数组。也就是说,它的顺序大概是这样:先按 age 排,age 相同再按 city 排,city 相同再按 score 排。

这句话比"最左前缀原则"更重要。

因为后面所有判断,都在问同一件事:你的查询条件有没有从左到右利用这条有序链。

比如:

sql 复制代码
SELECT * FROM user_profile
WHERE age = 25 AND city = 'Hangzhou';

这条能用上 age,city 两列定位。因为先找到 age = 25 的连续区间,再在这个区间里找 city = Hangzhou,顺序没有断。

再看:

sql 复制代码
SELECT * FROM user_profile
WHERE city = 'Hangzhou' AND score = 90;

这条不是因为 SQL 里没写 age 所以"心情不好不走索引",而是因为整棵索引树首先按 age 排。你跳过了第一列,city 在全局上不是连续有序的,优化器没法直接拿它做有序定位。

这才是最左前缀的本质。

范围条件为什么会让后面的列"断掉"

很多八股会写:范围查询后面的字段失效。

这句话容易误导。真正断掉的是"继续利用有序性做精确定位"的能力,不是后面的列完全没用了。

看这个:

sql 复制代码
SELECT * FROM user_profile
WHERE age = 25 AND city > 'Hangzhou' AND score = 90;

联合索引是 (age, city, score)

age = 25 可以定位到一个区间。city > 'Hangzhou' 也是范围条件,可以把这个区间继续缩小。

但到了 score = 90,问题来了:在 city > Hangzhou 这个范围里,数据是先按 city 分组排序的。不同 city 下面各自有自己的 score 顺序,score = 90 在整个剩余范围里不是一个连续区间。

所以 score 不能继续参与"定位区间"。

但它不一定完全没用。

如果 MySQL 能在索引条目里直接看到 score,它可能用 Index Condition Pushdown,也就是常说的 ICP,把一部分 WHERE 条件下推给存储引擎。官方手册里也写得很清楚:ICP 的目标是减少读取完整行的次数,EXPLAINExtra 里会看到 Using index condition

这就是很多人答错的地方:

"范围后面的列失效"太粗。

更好的答法是:它不能继续扩大联合索引的有序定位前缀,但可能作为索引条件下推提前过滤,减少回表。

函数、隐式转换和前缀通配符,问题都出在"列被藏起来了"

再看几个常见坑。

sql 复制代码
SELECT * FROM user_profile
WHERE age + 1 = 26;

很多人会说函数导致索引失效。严格讲,这里不是函数本身有魔法,而是索引里保存的是 age 的有序值,不是 age + 1 的有序值。

你把列包在表达式里,优化器没法直接按索引里的原始有序值定位。

如果改成:

sql 复制代码
SELECT * FROM user_profile
WHERE age = 25;

列裸露出来,索引才好用。

隐式转换也是类似问题。

假设 city 是字符串类型:

sql 复制代码
SELECT * FROM user_profile
WHERE city = 330100;

这类写法可能触发类型转换。实际转换方向和字段类型、字符集、优化器规则有关,不能在面试里一口咬死所有情况。但风险很清楚:一旦索引列被转换成表达式参与比较,就可能破坏原本的有序查找。

所以项目里最稳的写法是让参数类型和字段类型一致:

sql 复制代码
SELECT * FROM user_profile
WHERE city = '330100';

LIKE 也是同一个逻辑。

sql 复制代码
WHERE name LIKE '张%'

前缀是确定的,B+Tree 还能找到以"张"开头的一段范围。

sql 复制代码
WHERE name LIKE '%张'

前面是任意字符,开头不确定,没法从索引的有序头部开始定位。不是 LIKE 天生不能走索引,是前缀通配符把起点抹掉了。

type = index 不一定是好消息

面试官如果给你一段 EXPLAIN,别只盯着"有没有 key"。

我一般按这几个字段看:

key:优化器实际选了哪个索引。不是你建了哪个索引,它就一定用哪个。

type:访问方式。常见从好到差大概可以理解成 consteq_refrefrangeindexALL。但别把它当绝对排名,要结合行数和场景。

rows:估算要扫描多少行。这个值很大,哪怕 key 有东西,也可能只是扫了一个很宽的索引区间。

Extra:看有没有 Using indexUsing index conditionUsing whereUsing filesort

其中最容易误判的是 type = index

它不是"用了索引所以很好",很多时候意思是扫整棵索引。它可能比扫整张表轻一点,因为索引页更小;但本质上还是扫描,不是精确定位。

比如你只查索引里的列:

sql 复制代码
SELECT age, city, score FROM user_profile
WHERE score = 90;

因为查询列都在 idx_age_city_score 里,可能出现覆盖索引,不用回表。但 score 不是最左列,优化器可能还是要扫索引。这个时候你看到 Using index,不能直接说性能很好,只能说"覆盖索引避免了回表,但定位能力不强,要看扫描行数"。

这句话比背"覆盖索引很快"靠谱。

面试里怎么把这题答得像真的做过

别上来就报八条失效场景。

可以按这个顺序说:

第一,看联合索引顺序。它能不能从最左列开始形成连续的定位前缀。

第二,看条件形态。等值条件通常保留有序性,范围条件会让后续列不能继续做有序定位,函数、表达式、隐式转换可能把索引列藏起来。

第三,看是不是还有索引内过滤能力。后续列不能继续定位,不代表完全没用,可能通过 ICP 在存储引擎层提前过滤。

第四,用 EXPLAIN 验证。重点看 keytyperowsExtra,别只看有没有命中索引。

如果面试官让你现场优化一条慢 SQL,回答也可以这样落地:

sql 复制代码
-- 原 SQL
SELECT * FROM user_profile
WHERE city = 'Hangzhou' AND age = 25
ORDER BY score DESC;

如果已有索引是 (age, city, score),这条 SQL 即使 WHERE 顺序写成 city,age,优化器通常也能重排等值条件,不需要因为 SQL 文本顺序焦虑。

真正要看的是业务查询模式。

如果绝大多数查询都是按 age,city 过滤,再按 score 排序,这个索引顺序就合理。

如果业务经常只按 city 查,那 (age, city, score) 对它就不友好,可能需要单独的 city 索引,或者换成以 city 开头的联合索引。但这不是"多建几个索引总没错",写入成本、磁盘空间、优化器选择成本都要算进去。

我自己练这类题时,会把几种 SQL 丢进面灵的模拟面试里,让它连续追问"为什么这列不能继续定位""Using index conditionUsing index 有什么区别"。这种追问比刷清单有效,因为你很快会发现自己是哪一步讲不清。

最后给一个短答案模板

如果面试里只给你三十秒,可以这么答:

MySQL 联合索引不是多个单列索引拼起来,而是按索引列从左到右排序的一棵 B+Tree。判断一条 SQL 是否高效使用索引,要先看它能不能从最左列开始形成连续定位前缀。等值条件一般能继续往后用,范围条件后面的列通常不能继续参与有序定位,但可能通过 ICP 做索引内过滤。函数、表达式、隐式转换、前缀通配符的问题,本质是让优化器没法直接利用索引列的有序值。最后一定用 EXPLAIN 验证,看 keytyperowsExtra,不能只说"走了索引"或者"索引失效"。

这段能顺着讲下来,比背八条口诀抗追问得多。

相关推荐
tant1an3 小时前
MySQL面试题(一)
数据库·mysql
红糖奶茶3 小时前
MySQL 8.0的自增主键持久化特性
数据库·mysql·adb
uhakadotcom5 小时前
Scrapy-Redis 里面提供哪些即开即用的功能能力模块
前端·面试·github
xieliyu.5 小时前
MySQL 六大基础约束详解:NOT NULL/DEFAULT/UNIQUE/ 主键 / 外键 / CHECK
android·数据库·sql·mysql
KaifuZeng5 小时前
通信与接口协议面试十七、SFP光模块
单片机·嵌入式硬件·面试·通信与接口协议
Anokata5 小时前
MYSQL SQL 执行系列1
android·sql·mysql
weedsfly6 小时前
单例模式在前端中的正确打开方式
前端·javascript·面试
Java成神之路-6 小时前
彻底分清:垂直分库、水平分库、垂直分表、水平分表(附 Mycat 完整配置)
mysql
帅次6 小时前
Android 高级工程师面试:网络与数据层 近1年高频追问 18 题
android·网络·okhttp·面试·retrofit·gilde