面试被问「MySQL索引原理」,真正被考的不是能不能背出 B+ 树有几层,而是能不能把一条慢 SQL 归因到执行计划上,再说清为什么这个索引没被用上。所以准备顺序建议反过来:先能读懂 EXPLAIN 里的 type、key、rows、filtered 和 Extra,再从执行计划回推索引结构。下面按排查顺序展开。
被问 MySQL 索引原理,先把答案落在可核对的现象上:用 SHOW INDEX FROM 看索引长什么样,用 EXPLAIN 看优化器实际选了哪个索引。适用场景是有具体表结构和 SQL 的追问;操作动作是先跑 EXPLAIN 再讲原理;验证方式是 key 与 rows 是否和预期一致;边界是没有表结构时只能讲通用规律,别硬套某条 SQL 的结论。
MySQL索引原理及慢查询优化
「及慢查询优化」这半句在面试里对应的是「你怎么定位一条慢查询」,属于排查动作,不是原理题。判断对方问的是不是这一步,看问题里有没有「慢」字,有没有让你说定位过程;只要落到定位,通常要接慢日志和摘要表,而不是继续讲 B+ 树。
可核对的位置是慢日志开关和落盘路径,先确认它到底有没有开:
SHOW VARIABLES LIKE 'slow_query_log';
SHOW VARIABLES LIKE 'slow_query_log_file';
SHOW VARIABLES LIKE 'long_query_time';
SHOW VARIABLES LIKE 'log_output';
如果 slow_query_log 是 OFF,或者 log_output 里没有 FILE,那「我看慢日志」这句话就站不住。另一个可核对的位置是 performance_schema 的语句摘要表,能按平均耗时排序:
SELECT DIGEST_TEXT, COUNT_STAR, AVG_TIMER_WAIT/1000000000 AS avg_ms
FROM performance_schema.events_statements_summary_by_digest
ORDER BY AVG_TIMER_WAIT DESC
LIMIT 10;
字段名以本机实际表结构为准,不同版本可能有差异,用 DESC 看一眼再写全字段更稳。
MySQL索引原理和慢查询优化
这一小节对应的是「优化」那一半:你的 SQL 用了哪个索引、为什么是这个。判断面试官问的是不是这层,看他有没有让你写 EXPLAIN,或者问「这条 SQL 你会怎么改」。
可核对的位置就是 EXPLAIN 的输出字段,不需要凭感觉:
EXPLAIN SELECT id, user_id, amount
FROM t_order
WHERE user_id = 10086 AND status = 1
ORDER BY created_at DESC
LIMIT 20;
重点看三列:type 是从 ALL 走到 index、range、ref、eq_ref 还是 const;key 是实际选中的索引名,为 NULL 说明没走索引;rows 是预估扫描行数,要和 SELECT COUNT(*) FROM t_order 的结果放在一起比才有意义。Extra 里出现 Using filesort、Using temporary,说明排序或分组没吃到索引。
MySQL索引:先把能核对的事实摆清楚
对象是一张具体的表和一条具体的 SQL,前提是能用只读账号连上实例,现象是索引长什么样、优化器选了什么、扫了多少行。这一段先不下结论,只把能查到的摆出来。
SHOW CREATE TABLE t_order;看字段类型和字符集,字符串字段与数字比较时的隐式转换隐患从这里能看出来。SHOW INDEX FROM t_order;看 Key_name、Seq_in_index(联合索引的字段顺序)、Cardinality(区分度估算)、Collation(A 升序、D 降序)。SELECT COUNT(*) FROM t_order;给 EXPLAIN 的 rows 一个参照系。EXPLAIN FORMAT=JSON看 key_length、used_key_parts、attached_condition,比表格形式细。SELECT VERSION();确认版本。EXPLAIN 的字段含义和 EXPLAIN ANALYZE 是否可用跟版本有关,具体以本机HELP EXPLAIN输出或官方手册的 EXPLAIN 章节为准,不要凭记忆断言。
MySQL索引最容易搞错的一点
最常见的误解是把「这个字段建了索引」等同于「这条 SQL 会用它」。中间隔着优化器对区分度的估算:如果 where 条件能命中表里很大比例的行,回表成本高于全表扫,优化器可能直接选 type=ALL。这时问题不在「索引失效」,而在索引对这条 SQL 本来就没有选择性。
能把这两种情况分开的检查动作,是强制走一次索引做对比:
EXPLAIN SELECT id FROM t_order FORCE INDEX (idx_user_status)
WHERE user_id = 10086 AND status = 1;
如果强制后 rows 相比默认计划明显下降,说明索引是有效的,问题出在统计信息或数据分布,可以先 ANALYZE TABLE t_order; 再看;如果强制后 rows 几乎不动,说明这个索引对当前查询没有区分度,该改的是索引设计,而不是去查「索引失效的十种原因」。另一个常见诱因是字段被函数包住或左右类型不一致,这两种情况在 EXPLAIN 的 Extra 或 attached_condition 里能看到痕迹,属于能核对的范围。

MySQL索引:一个照着就能做完的做法
下面这条路径从开日志一路走到验证,每一步都写清该看到什么。
第一步,临时打开慢日志(SET GLOBAL 重启后失效,只适合临时排查):
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SET GLOBAL slow_query_log_file = '/var/lib/mysql/slow.log';
SHOW VARIABLES LIKE 'slow_query_log%';
该看到 slow_query_log 为 ON,且路径是你有权限读取的文件。要长期生效,改 my.cnf 的 [mysqld] 段再重启,别只依赖 SET GLOBAL。
第二步,从日志里捞出耗时最高的几条:
mysqldumpslow -s t -t 10 /var/lib/mysql/slow.log
-s t 按总耗时排序,-t 10 取前 10 条。该看到每组样本的 Query_time 和 Lock_time,先确认慢在查询本身还是锁等待。
第三步,对样本跑带实际执行信息的计划(较新版本才有,先 SELECT VERSION() 确认):
EXPLAIN ANALYZE SELECT id, user_id, amount
FROM t_order
WHERE user_id = 10086 AND status = 1
ORDER BY created_at DESC
LIMIT 20;
该看到 actual rows 与预估 rows 的对比,两者差得远,通常是统计信息偏差或数据分布不均,先 ANALYZE TABLE 再判断。
第四步,按最左前缀补联合索引:等值条件放前面,范围或排序列放后面,排序列的方向要和 ORDER BY 一致:
ALTER TABLE t_order ADD INDEX idx_user_status_created (user_id, status, created_at);
再跑一次同样的 EXPLAIN,该看到 key 变成 idx_user_status_created,Extra 里的 Using filesort 消失、rows 明显下降。如果 rows 没降,说明这个组合顺序不对,调整字段顺序再试。
最后一个容易踩的坑:不要只在小表或本地库上验证就上线。数据量少的时候优化器往往一律选全表扫,rows 的判断会失真;同时加索引会带来写放大,DDL 建议走在线变更工具或低峰期执行,MySQL 8.0 的 ADD INDEX 虽然支持 Online DDL,仍会占用 IO,具体行为以本机版本和 SHOW VARIABLES LIKE 'innodb_online_alter_log_max_size' 的实际值为准。