MySQL 慢查询与 EXPLAIN:怎么定位、怎么优化

📌 先说结论

有了索引,查询不一定就快——关键看查询"有没有真正用到索引"。

EXPLAIN 是 MySQL 的"执行计划分析器":不用真正执行查询,就能看到 MySQL 打算怎么查——走不走得索引、扫描多少行、用哪种访问方式。这是定位慢查询、验证索引效果的核心工具。

这篇用本机真实 EXPLAIN 输出,把关键字段、怎么读、怎么优化讲清楚。


一、先搞懂:慢查询为什么慢

慢查询的根因,绝大多数是"全表扫描"——MySQL 一行一行扫整张表去找数据,数据量大时当然慢。

走索引 vs 全表扫描:

  • 走索引:MySQL 按索引结构(B+树)快速定位到目标行,只扫很少的行;
  • 全表扫描:MySQL 把整张表每一行都扫一遍,扫描行数 = 表总行数。

所以优化查询的核心,就是"让查询走索引、避免全表扫描"。EXPLAIN 就是判断"有没有走索引"的工具。


二、EXPLAIN 怎么用

在 SQL 前加 EXPLAIN,就能看到 MySQL 的执行计划,而不会真正执行查询。

EXPLAIN SELECT * FROM user WHERE name='张三';

实测输出(本机 MySQL 8.0):

+------+-----------+-------+---------+--------+-------------+---------+---------+-------+------+----------+
| id   | select_type| table | partitions | type | possible_keys|    key | key_len |   ref | rows | filtered | Extra |
+------+-----------+-------+---------+--------+-------------+---------+---------+-------+------+----------+
|    1 | SIMPLE    | user  | NULL    | ref    | idx_name    | idx_name|   83    | const |    1 |  100.00  | NULL  |
+------+-----------+-------+---------+--------+-------------+---------+---------+-------+------+----------+

关键字段解读(你只需要记住这几个):

EXPLAIN 执行计划:看懂这几个字段

EXPLAIN 输出的核心字段,重点看 type、key、rows

字段含义怎么看
type访问类型(最重要)从好到差:const > eq_ref > ref > range > index > ALL(全表扫描,最差)
key实际用到的索引NULL = 没走索引;有值 = 走了该索引
rows预估扫描行数越小越好;全表扫描时 = 表总行数
possible_keys可能用到的索引优化器候选,不一定用
Extra额外信息Using index(覆盖索引)、Using where(过滤)等

核心判断:看 type 和 key——type 不是 ALL、key 有值,说明走了索引;type=ALL、key=NULL,说明全表扫描,需要优化。


三、实测:走索引 vs 不走索引

建一个表并加索引(实测):

CREATE TABLE user (id INT PRIMARY KEY, name VARCHAR(20), age INT, city VARCHAR(20));
ALTER TABLE user ADD INDEX idx_name(name);
ALTER TABLE user ADD INDEX idx_city_age(city, age);

场景一:走索引(实测)

EXPLAIN SELECT * FROM user WHERE name='张三';
type=ref,  key=idx_name,  rows=1

解读:type=ref(走索引等值匹配),key=idx_name(用了 name 索引),rows=1(只扫 1 行)。这是理想的执行计划。

场景二:联合索引 + 最左前缀(实测)

EXPLAIN SELECT * FROM user WHERE city='北京' AND age>25;
type=range, key=idx_city_age, rows=2, Extra=Using index condition

解读:联合索引 (city, age),从最左列 city 开始匹配(最左前缀),type=range(范围扫描),走了索引。

场景三:没走索引——不满足最左前缀(实测)

EXPLAIN SELECT * FROM user WHERE age>25;
type=ALL, key=NULL, rows=4

解读:只查 age,而 age 是联合索引的第二列,不满足"最左前缀",索引用不上,退化为全表扫描(type=ALL,key=NULL)。

这就是最典型的"建了索引却没走"——索引列的使用顺序很重要。


四、慢查询日志:先把"慢"抓出来

EXPLAIN 是"事后分析",慢查询日志是"事前抓取"——先得知道哪条 SQL 慢,才有得分析。

慢查询日志(slow query log) 是 MySQL 内置的功能:执行时间超过阈值的 SQL,会被自动记录到日志文件里。

开启方式(临时,重启失效):

-- 查看当前状态
SHOW VARIABLES LIKE 'slow_query_log';
SHOW VARIABLES LIKE 'long_query_time';

-- 开启慢查询日志,阈值设为 1 秒
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;

永久生效要写进配置文件 my.cnf([mysqld] 段):

[mysqld]
slow_query_log = ON
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = ON

几个关键参数:

参数作用
slow_query_log是否开启慢查询日志
long_query_time阈值(秒),超过才记录,默认 10
slow_query_log_file日志文件路径
log_queries_not_using_indexes记录没走索引的 SQL(即使很快)

看日志内容:日志里每条记录包含执行时间、耗时、扫描行数、锁等待时间、SQL 原文,例如:

# Time: 2026-11-15T10:20:31.123456+08:00
# User@Host: root[root] @ localhost []
# Query_time: 2.301  Lock_time: 0.000  Rows_sent: 1  Rows_examined: 1000000
SET timestamp=1763173231;
SELECT * FROM user WHERE age > 25;

重点看 Query_time(耗时)和 Rows_examined(扫描行数)——扫描行数远大于返回行数,基本就是没走索引。

不想翻日志文件?用 mysqldumpslow 汇总:

# 按总耗时排序,取前 10 条
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

拿到慢 SQL 之后,再交给 EXPLAIN 分析执行计划——这就是下一节的完整流程。


五、慢查询排查的完整流程

定位一条慢 SQL,按这个顺序:

① 找到慢查询。开启慢查询日志,或用 SHOW PROCESSLIST 看当前执行的 SQL,定位到具体语句。

② 跑 EXPLAIN。对这条 SQL 加 EXPLAIN,看执行计划。

③ 看 type 和 key。type 是不是 ALL?key 是不是 NULL?

④ 判断优化方向:

  • type=ALL、key=NULL → 没走索引,检查是不是没有索引,或索引因写法失效(见下一篇"索引失效");
  • 走了索引但 rows 大 → 索引选得不好,考虑建更合适的索引(如联合索引、覆盖索引);
  • rows 很小但还是慢 → 问题可能在其他环节(如锁等待、锁冲突、回表多、数据量本身大),不只是索引。

⑤ 加索引后,用 EXPLAIN 验证效果。加索引不等于一定有效,一定要用 EXPLAIN 验证。

💡 一个重要提醒:EXPLAIN 是"预估",基于统计信息,不是精确结果;但它足以判断"走不走索引、大概扫多少行",是优化的第一工具。—

六、优化手段(在索引之外)

除了建索引,还有几个常用优化手段:

① 覆盖索引。查询的列都包含在索引里,就不用再"回表"去主键索引取数据,Extra 会显示 Using index,更快。

**② 避免 SELECT ***。只查需要的列,减少数据传输和回表。

③ 避免大分页。LIMIT 100000, 20 这种深分页要扫描大量行,改用"先定位、再分页"(如 WHERE id > 上一页最大id LIMIT 20)。

④ 避免在索引列上运算/函数。这会导致索引失效(下一篇详细讲)。

⑤ 数据量本身过大。索引也救不了千万级单表的复杂查询,这时要考虑分库分表(见后续篇)。—

七、面试常问的 4 个问题

1. EXPLAIN 里 type 字段怎么看?

答:type 是访问类型,从好到差:const(主键/唯一索引等值,最好)、eq_ref、ref(普通索引等值)、range(范围)、index(扫描索引)、ALL(全表扫描,最差)。一般看到 ALL 就要警惕,看 key 是不是 NULL。

2. 怎么判断一个查询有没有走索引?

答:看 EXPLAIN 的 key 字段——有值说明走了该索引,NULL 说明没走;再看 type——不是 ALL 基本说明走了索引。rows 字段看预估扫描行数,越小越好。

3. 联合索引为什么要满足最左前缀?

答:联合索引 (a,b,c) 是按 a 排序、a 相同按 b、b 相同按 c 组织的。查询条件从最左列 a 开始,才能利用索引的有序性快速定位;跳过 a 直接查 b,索引无法有效利用,退化为全表扫描(实测 WHERE age>25 对 (city,age) 索引就是全表扫描)。

4. 慢查询的排查思路?

答:先开启慢查询日志/SHOW PROCESSLIST 定位慢 SQL → EXPLAIN 看执行计划 → 看 type/key 判断有没有走索引 → 没走索引就建索引/改写法,走了索引但 rows 大就优化索引 → 加索引后用 EXPLAIN 验证效果。—

八、总结

  1. 慢查询的根因多为全表扫描,优化核心是让查询走索引。
  2. EXPLAIN 是分析执行计划的核心工具,重点看 type(访问类型)、key(是否走索引)、rows(扫描行数)。
  3. 实测对比:走索引 type=ref/key 有值;不满足最左前缀(只查第二列)则 type=ALL、key=NULL,退化为全表扫描。
  4. 排查流程:定位慢 SQL → EXPLAIN → 看 type/key → 建索引/改写法 → 用 EXPLAIN 验证效果。

一句口诀:

慢查先看 EXPLAIN,type 别是 ALL;key 有值走索引,rows 越小越理想。


如果本文对你有帮助,欢迎点赞 + 收藏 + 关注!如果疑问或补充,欢迎在评论区交流~

关注我,后续分享更多 Java 核心知识点和实战技巧!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐