MySQL执行计划分析
版本说明
本文写于 2022-03,基于 MySQL 8.0。EXPLAIN 核心字段含义未变,经 2026-07 复核仍适用。
# MySQL执行计划分析
在数据库性能优化中,分析和理解SQL语句的执行计划是非常重要的一步。MySQL 提供了 EXPLAIN 语句来查看查询的执行计划。通过 EXPLAIN,我们可以了解查询是如何执行的,并找出可能的性能瓶颈。
# 使用 EXPLAIN 命令
你可以通过在查询前加上 EXPLAIN 关键字来查看执行计划。例如:
EXPLAIN SELECT * FROM users WHERE id = 1;
# 执行计划的各个字段解释
执行计划返回的结果包含多个字段,每个字段都有其特定的含义。以下是常见字段的解释:
- id:查询的序列号,表示查询中执行 select 子句或操作表的顺序。
- id相同时,执行顺序由上至下
- id不同时,id值越大优先级越高,越先执行
- select_type:查询的类型,常见的有:
SIMPLE:简单的 SELECT 查询,不包含子查询或 UNIONPRIMARY:最外层的 SELECTSUBQUERY:子查询中的第一个 SELECTDERIVED:派生表(在FROM子句中的子查询)UNION:UNION中第二个及后面的SELECT语句UNION RESULT:UNION的结果
- table:当前查询操作的表名或别名。
- partitions:匹配的分区信息(如果查询是基于分区表)。
- type:访问类型,表示MySQL如何查找表中的行,性能从好到差依次是:
system:表只有一行记录const:通过索引一次就找到了,用于主键或唯一索引查询eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配ref:非唯一性索引扫描,返回匹配某个单独值的所有行range:只检索给定范围的行,使用一个索引来选择行index:全索引扫描,遍历整个索引树ALL:全表扫描,MySQL遍历全表来找到匹配的行
- possible_keys:查询可能使用的索引。
- key:实际使用的索引。如果为NULL,则没有使用索引。
- key_len:使用的索引的长度。在不损失精确性的情况下,长度越短越好。
- ref:显示索引的哪一列被使用了。
- rows:MySQL认为必须检查的行数(估算值)。
- filtered:表示返回结果的行数占需读取行数的百分比。
- Extra:包含不适合在其他列中显示但十分重要的额外信息:
Using index:使用覆盖索引Using where:使用WHERE过滤Using temporary:使用临时表Using filesort:需要额外的排序操作Using join buffer:使用连接缓冲Impossible WHERE:WHERE子句永远为false
# 关键访问类型详解
访问类型(type字段)是执行计划中最重要的指标之一,它显示了MySQL如何查找所需数据。按照性能从好到差的顺序:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
主要类型说明:
system/const:
- system:表中只有一行数据
- const:基于主键或唯一索引的等值查询
eq_ref:
- 出现在多表连接中
- 使用主键或唯一非空索引关联
ref:
- 使用非唯一索引或唯一索引的前缀匹配
- 返回匹配某个单独值的记录行
range:
- 索引范围扫描
- 常见于
<、>、BETWEEN、IN、LIKE等操作
index:
- 全索引扫描
- 相比ALL快,因为索引文件通常比数据文件小
ALL:
- 全表扫描
- 性能最差,应尽量避免
# 优化建议
索引优化:
- 在WHERE和JOIN条件中的列上建立适当的索引
- 考虑使用复合索引,注意索引列的顺序
- 避免过多索引,可能影响更新性能
查询优化:
- 只查询需要的列,避免
SELECT * - 使用LIMIT限制结果集大小
- 避免使用
SELECT DISTINCT,考虑用其他方式替代
- 只查询需要的列,避免
表结构优化:
- 选择合适的数据类型,尽量使用较小的数据类型
- 适当分表分库,控制单表数据量
监控与维护:
- 定期分析慢查询日志
- 使用
ANALYZE TABLE更新统计信息 - 合理设置
long_query_time参数
# 实际案例分析
考虑以下查询:
EXPLAIN SELECT * FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.order_date >= '2023-01-01'
AND o.status = 'completed';
2
3
4
示例执行计划分析:
| id | select_type | table | type | possible_keys | key | rows | Extra |
|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | o | range | idx_date | idx_date | 1000 | Using where |
| 1 | SIMPLE | u | eq_ref | PRIMARY | PRIMARY | 1 | NULL |
分析要点:
- 连接查询中orders表使用范围扫描
- users表通过主键关联,使用eq_ref访问类型
- 可以考虑创建复合索引(order_date, status)来优化性能
通过对执行计划的深入理解和分析,我们可以更好地优化查询性能,提高数据库效率。
# 配合慢查询日志定位问题:全量记录所有语句
EXPLAIN 分析的前提是你已经知道"哪条 SQL 有问题"。生产环境排查性能问题时,第一步往往是打开慢查询日志(slow log)找出可疑语句。默认的 long_query_time(通常 10 秒)只会记录明显超时的查询,但很多性能问题其实是"单条查询不算慢,但被高频调用",这类语句永远进不了默认的慢日志。
临时开启全量记录(把阈值降到 0,记录所有执行过的语句)用于短时间抓取分析:
mysql> SET GLOBAL long_query_time = 0;
Query OK, 0 rows affected (0.00 sec)
mysql> SET GLOBAL min_examined_row_limit = 1;
Query OK, 0 rows affected (0.00 sec)
2
3
4
5
long_query_time = 0 表示"任何耗时的查询都记";min_examined_row_limit = 1 进一步过滤掉那些扫描 0 行的空跑语句,避免日志被心跳/健康检查类查询淹没。
验证:
mysql> SHOW VARIABLES LIKE 'slow_query_log%';
mysql> SHOW VARIABLES LIKE 'long_query_time';
2
确认 slow_query_log 为 ON 且 long_query_time 已变为 0.000000,然后跑几条业务查询,tail -f 慢日志文件应该能立刻看到新记录。
坑:这是一把双刃剑——开启全量记录后,慢日志会以业务 QPS 的速度疯狂增长,磁盘 I/O 和磁盘空间都会明显上升,只应该临时开启、抓取几分钟到几十分钟数据后立刻改回原值,绝不能作为生产环境的常态配置。抓完数据用 mysqldumpslow 或 pt-query-digest 聚合分析,找到 TOP N 高频/高耗时语句后,再对照本文前面的 EXPLAIN 方法逐条排查执行计划。
- 01
- Nginx 运维知识地图:从配置基础到反向代理实战 原创07-29
- 02
- MySQL 运维知识地图:从入门配置到高可用排障 原创07-29