
这期拆解 MySQL 索引失效面试的 5 个高频扣分点:如何看 EXPLAIN、判断复合索引顺序、解释范围条件和 LIKE 边界,并把答案落到可验证的排查路径上。
EXPLAIN,再判断索引有没有被选中、用了几列、扫了多少行,最后把改法落到业务查询条件上。| 现场现象 | 你该怎么解释 | 面试官在看什么 |
|---|---|---|
key 是 NULL | 优化器没有选中可用索引,要回到 WHERE 条件和索引定义检查 | 你会不会看执行计划 |
key 有值,但 key_len 很短 | 只用了复合索引的一部分,后面的列没有参与定位 | 你是否理解复合索引顺序 |
type 是 range 或 index | 可能不是点查,而是在扫一段索引或整棵索引 | 你能不能区分「走索引」和「高效」 |
rows 很大、filtered 很低 | 预估要检查很多行,再靠条件过滤 | 你会不会看扫描量和过滤比例 |
EXPLAIN 会给出 possible_keys、key、key_len、rows、filtered、type、Extra 等字段;其中 key 表示实际选择的索引,key_len 能帮助判断复合索引用到了多长的前缀,rows 是预估检查行数,filtered 是按表条件过滤后的预估比例。2我不会先假设它一定该走某个索引。我会先看EXPLAIN:possible_keys说明理论候选索引,key说明实际选中的索引。如果possible_keys有值但key是NULL,说明优化器评估后没有选;如果key有值,还要继续看type、key_len、rows和Extra,判断它是不是高效使用。
CREATE INDEX idx_status ON orders(status);
SELECT *
FROM orders
WHERE status IN ('paid', 'shipped', 'closed');idx_status 值得用。你不能只说「索引失效了」,更应该说:这个条件区分度低,可能需要结合其他高选择性条件,或者接受它本来就不是一个适合单列索引解决的问题。(col1, col2, col3),可用于 (col1)、(col1, col2)、(col1, col2, col3) 这样的左侧前缀。3 反过来,只查 (col2) 或 (col2, col3),并不构成这个索引的左侧前缀,不能用它做查找。3CREATE INDEX idx_user_status_time ON orders(user_id, status, created_at);
SELECT *
FROM orders
WHERE user_id = 1001
AND status = 'paid'
AND created_at >= '2026-07-01';user_id 和 status 是等值条件,created_at 是范围条件。你的回答可以这样组织:我会把高频等值过滤条件放在复合索引前面,再把范围或排序字段放在后面。这样查询可以先定位到某个用户、某类订单,再在时间范围里扫描。如果索引写成(created_at, user_id, status),它可能先按时间扫一段,再过滤用户和状态,扫描量会变大。
>、<、>=、<=、BETWEEN、!=、LIKE 等范围类操作符时,它会使用这个操作符确定区间,但不再继续用后续 key part 构造这个区间。4CREATE INDEX idx_user_time_status ON orders(user_id, created_at, status);
SELECT *
FROM orders
WHERE user_id = 1001
AND created_at >= '2026-07-01'
AND status = 'paid';created_at >= ... 是范围条件。它可能只能先定位 user_id,再扫 created_at 的一段区间,status 更像是在这段结果里继续过滤,而不是继续精确缩小索引区间。CREATE INDEX idx_user_status_time ON orders(user_id, status, created_at);如果查询固定是「某个用户 + 某个状态 + 时间范围」,我会优先考虑(user_id, status, created_at)。因为前两列是等值条件,能先缩小范围;时间字段放到后面做范围扫描。原来的(user_id, created_at, status)不是完全没用,但status很可能不能继续参与区间定位。
LIKE 只答成「模糊查询不走索引」LIKE 题也不能答得太粗。LIKE 'ab%' 这种不以通配符开头的常量模式可以形成范围条件;但类似 LIKE '%b' 的条件不能用于构造范围扫描,文档示例里它会在提取范围条件时被移除。4LIKE 都不走索引」,要分清两类:| 写法 | 面试解释 | 更好的表述 |
|---|---|---|
name LIKE 'zhang%' | 前缀固定,可以按索引有序性定位一段范围 | 可能走范围扫描,但还要看数据量和执行计划 4 |
name LIKE '%zhang' | 左侧不固定,无法从索引开头确定范围 | 通常不能靠普通 BTREE 索引做高效查找 4 |
LOWER(name) = 'zhang' | 对列做函数,普通索引列值不再直接参与比较 | 要考虑改写条件、增加规范化字段或函数索引,具体看数据库版本和业务约束 |
如果业务需要前缀搜索,比如按手机号、订单号前缀查,可以把输入限制成右模糊,并配合合适索引。如果业务必须支持任意包含搜索,普通 BTREE 索引不适合硬扛,要考虑搜索引擎、倒排索引、额外冗余字段,或者先用更强的结构化条件缩小范围。
ALL 就只会说「加索引」rows 预估是不是很大。filtered 是不是很低。WHERE 里的条件能不能改成适合索引的形式。rows 和 filtered 的解释是:rows 表示预计要检查的行数,InnoDB 下它是估算值;filtered 表示按表条件过滤后的估计比例,rows × filtered 可以估算和下一张表连接的行数。2我会先确认这是不是一个真的慢查询。如果表很小,ALL未必值得优化;如果rows很大、filtered很低,就说明扫描量和过滤效率都有问题。接着我会看条件是否命中了复合索引左侧前缀,有没有函数、隐式类型转换、左模糊、低区分度字段单独建索引这些问题。最后再决定是改 SQL、改索引顺序,还是调整业务查询入口。
CREATE INDEX idx_user_time_status ON orders(user_id, created_at, status);
SELECT *
FROM orders
WHERE user_id = 1001
AND status = 'paid'
AND created_at BETWEEN '2026-07-01' AND '2026-07-31';我会先跑EXPLAIN,看possible_keys、key、key_len、type、rows和filtered。如果实际用了idx_user_time_status,但key_len显示只用到user_id和created_at这一段,我会怀疑status没有继续参与索引区间定位。 原因是这个复合索引顺序是(user_id, created_at, status),created_at BETWEEN ...是范围条件。MySQL 在复合索引上遇到范围条件后,后面的列通常不能继续用来构造更窄的区间,只能在扫出来的范围里过滤。 如果这个查询是高频入口,我会考虑把索引改成(user_id, status, created_at)。这样先按用户和状态做等值过滤,再按时间范围扫描。改完之后还要重新看EXPLAIN,确认rows是否下降、key_len是否更符合预期,不能只凭改了索引就宣布优化完成。
| 自查问题 | 合格回答应该包含什么 |
|---|---|
| 什么叫索引失效? | 不是泛泛说「没走索引」,而是能结合 EXPLAIN 看 key、key_len、type、rows、filtered |
| 复合索引怎么判断? | 能说清左侧前缀,能用 (a,b,c) 举出哪些查询能用、哪些不能用 |
| 范围条件为什么危险? | 能解释范围列之后的列可能不再参与区间定位,而不是只背「范围后失效」 |
LIKE 怎么答? | 能区分 abc% 和 %abc,知道前缀模式与左模糊不是一回事 |
| 看到全表扫描怎么办? | 先判断扫描量和过滤比例,再决定改条件、改索引还是接受这个查询形态 |
| 怎么证明优化有效? | 改完再看 EXPLAIN,必要时对比真实耗时、慢日志和线上数据分布 |
LIKE 查询一定不走索引。」dev.mysql.com
dev.mysql.com
dev.mysql.com
dev.mysql.com
This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.