慢查询是后端开发面试中的高频考点,也是日常工作中绕不开的实战问题。很多同学能说出“开慢查询日志”“用 EXPLAIN 看执行计划”,但一旦追问 type 字段有哪些取值、Extra 里出现 Using filesort 意味着什么,就容易卡壳。这篇文章从慢查询的定位手段讲起,再逐字段拆解 EXPLAIN 的输出,帮你把这条链路彻底打通。
一、慢查询怎么定位
1. 开启慢查询日志
MySQL 提供了慢查询日志来记录执行时间超过阈值的 SQL:
-- 查看当前配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
-- 动态开启(重启失效,永久生效需写入 my.cnf)
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = ON;
long_query_time 默认是 10 秒,生产环境通常调到 1 秒甚至更低。log_queries_not_using_indexes 会把未走索引的 SQL 也记录下来,方便发现潜在问题。
2. 用 mysqldumpslow 或 pt-query-digest 分析
慢查询日志本身是文本,直接看效率低。MySQL 自带的 mysqldumpslow 可以按执行时间、次数排序:
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
更推荐 Percona Toolkit 的 pt-query-digest,它能做聚合分析,输出每条 SQL 的执行次数、总耗时占比、平均耗时等,定位 TOP SQL 非常高效。
3. 实时查看:SHOW PROCESSLIST
对于正在执行的慢 SQL,可以用:
SHOW FULL PROCESSLIST;
关注 Time 列数值大、State 为 Sending data 或 Copying to tmp table 的线程。紧急情况下可以用 KILL 终止。
4. performance_schema
MySQL 5.7+ 的 performance_schema 提供了更细粒度的监控。比如 events_statements_summary_by_digest 表可以按 SQL 指纹聚合统计,不依赖日志文件:
SELECT DIGEST_TEXT, COUNT_STAR, AVG_TIMER_WAIT
FROM performance_schema.events_statements_summary_by_digest
ORDER BY AVG_TIMER_WAIT DESC LIMIT 10;
定位到具体 SQL 之后,下一步就是用 EXPLAIN 分析它为什么慢。
二、EXPLAIN 执行计划逐字段解读
EXPLAIN 的输出有 12 个左右的字段,面试中重点考察的是 id、select_type、type、key、rows、Extra 这几个。
id
查询的序列号。id 相同表示执行顺序从上到下;id 不同时,id 越大越先执行;id 为 NULL 表示这是一个结果集,不需要访问表(比如 SELECT 1)。
select_type
查询类型,常见取值:
- SIMPLE:简单查询,不含子查询或 UNION。
- PRIMARY:包含子查询时,最外层查询标记为 PRIMARY。
- SUBQUERY:SELECT 或 WHERE 列表中的子查询。
- DERIVED:FROM 子句中的子查询,结果放在临时表。
- UNION:UNION 中第二个及之后的 SELECT。
table
当前行正在访问的表名。如果是派生表,会显示 <derivedN>。
type(重点)
访问类型,从优到劣依次为:
- system:表只有一行,是 const 的特例。
- const:通过主键或唯一索引一次定位,最多返回一行。
- eq_ref:多表 JOIN 时,被驱动表通过主键或唯一索引等值匹配,每行只返回一条。
- ref:通过普通索引等值匹配,可能返回多行。
- range:索引范围扫描,如
BETWEEN、>、<、IN。 - index:全索引扫描,遍历整棵索引树,比 ALL 稍好(因为索引文件通常比数据文件小)。
- ALL:全表扫描,最差,必须优化。
面试中常问:range 和 index 哪个好?答案是 range,因为它只扫描索引的一部分,而 index 要扫全部索引。
possible_keys 和 key
possible_keys 是理论上可能用到的索引,key 是实际选择的索引。如果 possible_keys 有值但 key 为 NULL,说明优化器认为走索引不划算(比如索引区分度太低),或者统计信息过期。
key_len
索引使用的字节数。可以据此判断联合索引用到了几个字段。比如联合索引 (a, b, c),如果 key_len 只覆盖了 a 的长度,说明只用到了第一个字段。计算规则:字符集为 utf8mb4 时,一个字符占 4 字节,varchar 还要额外加 2 字节(长度前缀),允许 NULL 再加 1 字节。
ref
显示索引的哪一列被使用了,常见值是 const(常量)、func 或具体的列名。
rows
预估需要扫描的行数,不是精确值。这个数字越小越好,但要注意它基于统计信息,可能不准。
filtered
表示按条件过滤后剩余行的百分比。rows × filtered% 约等于实际参与后续 JOIN 的行数。这个值在 MySQL 5.7 之前只对 JOIN 有意义,5.7 之后单表查询也会显示。
Extra(重点)
这一列信息量最大,常见取值:
- Using index:覆盖索引,查询的列全部在索引中,不需要回表。这是好现象。
- Using where:在存储引擎检索后再用 WHERE 条件过滤,说明索引没有完全覆盖过滤条件。
- Using filesort:无法利用索引排序,需要在内存或磁盘上做额外排序。出现这个通常意味着 ORDER BY 的字段没有走索引,需要优化。
- Using temporary:使用了临时表,常见于 GROUP BY 或 DISTINCT 且无法利用索引的情况。这个开销很大,应尽量避免。
- Using join buffer:JOIN 时被驱动表没有走索引,用了连接缓冲区。说明被驱动表的关联字段缺索引。
- Impossible WHERE:WHERE 条件恒为假,比如
WHERE 1=0。
三、实战优化思路
拿到 EXPLAIN 结果后,优化方向可以按以下优先级排查:
- type 为 ALL:检查 WHERE 条件字段是否有索引,考虑加索引。
- key 为 NULL 但 possible_keys 有值:更新统计信息(
ANALYZE TABLE),或检查索引区分度。 - Extra 出现 Using filesort:让 ORDER BY 走索引。注意联合索引的顺序,排序字段要放在索引的合适位置。
- Extra 出现 Using temporary:GROUP BY 字段加索引,或改写 SQL 减少临时表使用。
- rows 很大但实际返回少:考虑索引选择性,必要时用复合索引或覆盖索引。
一个常见误区是“加了索引就一定快”。实际上索引过多会影响写入性能,而且优化器可能因为统计信息不准而选错索引。必要时可以用 FORCE INDEX 强制指定,但更推荐通过 ANALYZE TABLE 让优化器自己做对选择。
小结
慢查询定位的核心链路是:慢查询日志或 performance_schema 找到 TOP SQL,再用 EXPLAIN 分析执行计划。EXPLAIN 中 type 反映访问效率,key 反映索引使用情况,Extra 揭示排序、临时表等额外开销。面试中能把这些字段讲清楚,并结合实际场景给出优化方案,基本就能拿到不错的评价。建议在本地环境建几张有数据的表,亲手跑一遍各种查询的 EXPLAIN,比死记硬背字段含义有效得多。
未经允许不得转载:任鹏个人博客 » MySQL 慢查询定位与 EXPLAIN 执行计划逐字段解读

