MySQL 慢查询与 EXPLAIN:怎么定位、怎么优化
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 输出的核心字段,重点看 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 验证效果。—
八、总结
- 慢查询的根因多为全表扫描,优化核心是让查询走索引。
- EXPLAIN 是分析执行计划的核心工具,重点看 type(访问类型)、key(是否走索引)、rows(扫描行数)。
- 实测对比:走索引 type=ref/key 有值;不满足最左前缀(只查第二列)则 type=ALL、key=NULL,退化为全表扫描。
- 排查流程:定位慢 SQL → EXPLAIN → 看 type/key → 建索引/改写法 → 用 EXPLAIN 验证效果。
一句口诀:
慢查先看 EXPLAIN,type 别是 ALL;key 有值走索引,rows 越小越理想。
如果本文对你有帮助,欢迎点赞 + 收藏 + 关注!如果疑问或补充,欢迎在评论区交流~
关注我,后续分享更多 Java 核心知识点和实战技巧!
更多推荐


所有评论(0)