1. 为什么上篇发完之后,我反而越写越谨慎
上篇整理了一份“索引失效场景清单”,从最左前缀到隐式转换,从前导通配符到 or 条件,把常见的坑都过了一遍。发出去之后评论区很热闹,有人收藏,有人问“能不能直接给个全表禁止清单”,也有人问了一个我特别想展开的问题:既然失效场景这么多,那建索引到底还有没有用?
说实话,这个问题比“有哪些场景会失效”更难回答。因为索引失效不是背八个场景就能避开的,真正决定索引能不能用上的,是 MySQL 优化器基于成本、统计信息、B+树结构特性做出的一个综合决策。你背了“不要对列使用函数”,但如果不知道优化器为什么不能对函数列使用索引,换个 not in、or、range 查询,照样迷糊。
这篇可以作为上一篇的续篇来读。我会抛开“面试八股”式的场景清单,聊一聊索引失效背后的几个共性问题:为什么同一个 SQL,表数据量不同,执行计划完全不同?为什么明明建了联合索引,排序还是用了 filesort?为什么优化器偶尔会“放弃”索引,而选择全表扫描?这些才是索引失效真正值得思考的地方。
顺便说一句:索引失效不等于 SQL 一定慢。某些情况下,优化器放弃索引恰恰是最优解。如果你抱着“索引一旦失效就必须改”的思路去调优,反而可能把好的执行计划改成坏的。这里面的分寸,需要在真实案例里慢慢去摸。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 用 explain 重新认识“失效”:先从访问路径找源头
排查索引失效,我一般不看概念,直接看执行计划。因为“失效”是一个结果,结果背后的成因五花八门。explain 输出的一堆字段里,最值得先盯的其实是 type、key、rows、filtered 这四列。
2.1 type 的含义决定了对“失效”的基本判断
先看一个最简单的例子。假设我们有一张用户订单表,user_id 上建了普通索引:
sql复制CREATE TABLE t_order (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
order_no VARCHAR(64) NOT NULL,
status TINYINT NOT NULL DEFAULT 0,
amount DECIMAL(10, 2) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL,
KEY idx_user_id (user_id)
) ENGINE=InnoDB;
执行下面这条 SQL,然后看 explain:
sql复制EXPLAIN SELECT * FROM t_order WHERE user_id = 100;
多数情况下你会看到 type 为 ref,key 为 idx_user_id,rows 大概是该用户对应的记录数。这说明索引正常生效。
但如果我把查询改成这样:
sql复制EXPLAIN SELECT * FROM t_order WHERE user_id + 1 = 100;
type 会变成 ALL,key 变成 NULL。这就是“索引失效”的典型样例。但失效的本质不是 MySQL 不认 user_id 上的索引,而是 B+树索引的定位逻辑是在索引键原始值上进行的。user_id + 1 之后,索引里存储的键值没有经过 +1 的加工,优化器无法在树上执行等值比较,只能把整棵索引遍历完之后再对每条记录做表达式计算,成本自然就高于全表扫描。
这里的关键认知是:失效的是“索引快速定位”的能力,而不是索引文件本身。 索引还在,但无法用于 ref、range 这类快速定位访问,最多只能退化成 index 类型的全索引扫描。type=ALL 比 type=index 更糟,前者直接扫聚簇索引叶子节点,后者至少能扫一个较小的二级索引树。
2.2 区分“索引失效”和“索引效率不高”
看 explain 时很多人只看 key 是否为 NULL,这是不够的。比如这条查询:
sql复制EXPLAIN SELECT order_no FROM t_order WHERE status = 0;
status 上有索引,select 列也在索引里,type 是 ref,看起来没问题。但如果 status=0 的记录占了全表的 90%,优化器可能直接走全表扫描,此时 key 是 NULL。这是失效吗?不是。这是优化器经过成本计算后认为,使用索引需要大量回表,还不如直接扫全表。
所以在讨论“索引失效”之前,先要区分三种情况:
| 现象 | 本质 | 应对方式 |
|---|---|---|
| key=NULL,type=ALL | 无法使用索引定位 | 改写 SQL,让查询条件匹配索引 |
| key 有值,但 rows 极大 | 索引效率不高,区分度差 | 重新评估索引选择性和查询模型 |
| key=NULL,type=ALL | 优化器成本判断后选择全表 | 不一定要改 SQL,小心过度调优 |
上篇讲的各种失效场景,绝大多数属于第一类——语法上没有利用索引的机制,例如前导通配符、隐式转换、函数运算、不满足最左前缀等。遇到这类问题,应该先检查 SQL 写法,再检查索引设计。
2.3 用 explain 看一个隐式转换的例子
隐式转换是面试常考、线上也最高发的场景之一。假
