执行计划分析:EXPLAIN命令详解、type列解读、Extra信息含义、慢查询日志配置与分析

说实话,很多开发同学写SQL的时候,心里是没底的。写完一跑,能出结果就行。但性能好不好?会不会把数据库拖垮?没人知道。

我个人的习惯是,任何上线前的SQL,必须过一遍执行计划。这就像开车前看一眼仪表盘,心里才有底。今天我们就来聊聊这个“仪表盘”怎么看。

EXPLAIN命令:你的SQL透视镜

EXPLAIN 是 MySQL 提供的一个神器。你只需要在 SQL 前面加上 EXPLAIN 关键字,它就会告诉你 MySQL 打算怎么执行这条语句。

EXPLAIN SELECT * FROM users WHERE age > 30;

输出结果是一张表,每一行代表一个查询步骤。关键列有这些:

列名 含义
id 查询的标识符,id越大越先执行
select_type 查询类型,比如SIMPLE、PRIMARY、SUBQUERY
table 正在访问哪张表
type 访问类型,性能优化的核心指标
possible_keys 可能用到的索引
key 实际用到的索引
key_len 索引使用的字节数
ref 索引的哪一列被使用了
rows 预估需要扫描的行数
Extra 额外的补充信息,经常藏着大坑
我的小习惯: 看执行计划时,我一般先扫一眼 type 和 Extra。这两列如果出现“坏信号”,基本就可以断定这条SQL需要优化了。

type列解读:从全表扫描到常量查找

type 列描述了 MySQL 如何查找数据行。性能从好到差,大致是这样:

type 性能 说明
system 最好 表只有一行,基本见不到
const 极好 通过主键或唯一索引查找,只匹配一行
eq_ref 很好 多表连接时,使用主键或唯一索引
ref 好 使用普通索引,返回多行
range 一般 索引范围扫描,比如 BETWEEN、>、IN
index 较差 扫描整个索引树,比全表快一点
ALL 最差 全表扫描,性能杀手

我在项目中遇到过好几次,线上慢查询的 type 都是 ALL。有一次查一个订单表,数据量才几十万,但每次查询都要好几秒。一看执行计划,type 是 ALL,rows 显示扫描了 30 万行。加了个索引后,type 变成了 ref,查询时间降到了 10 毫秒以内。

注意: 如果看到 type 是 ALL 或 index,而且表数据量很大,这基本就是慢查询的根源。赶紧检查索引吧。

Extra信息含义:隐藏的细节

Extra 列里经常出现一些“暗语”,我来翻译几个最常见的:

  • Using index:覆盖索引。查询所需的数据都在索引里,不需要回表。这是好事。
  • Using where:在存储引擎层过滤后,还需要在服务层再过滤。通常意味着索引没有完全覆盖查询条件。
  • Using temporary:使用了临时表。常见于 GROUP BY 和 ORDER BY 字段不一致时。性能杀手。
  • Using filesort:文件排序。无法利用索引排序,MySQL 不得不自己排序。数据量大时很慢。
  • Using index condition:索引下推。MySQL 5.6 以后引入的优化,能在索引层面过滤数据,减少回表次数。

嗯,这里要注意。如果看到 Using temporary 和 Using filesort 同时出现,那基本就是“双重暴击”。我曾经优化过一个报表查询,Extra 里这两个都出现了,查询跑了 20 秒。后来通过调整索引顺序,让 GROUP BY 和 ORDER BY 用上了同一个索引,查询时间直接降到了 0.5 秒。

慢查询日志:找到那些“拖后腿”的SQL

执行计划是分析单条SQL的。但线上那么多SQL,你怎么知道哪些是慢的?这就需要慢查询日志了。

配置慢查询日志,通常有两种方式:

方式一:修改配置文件(永久生效)

# 在 my.cnf 或 my.ini 中添加
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 2
log_queries_not_using_indexes = 1

方式二:动态设置(无需重启)

SET GLOBAL slow_query_log = 'ON';
SET GLOBAL slow_query_log_file = '/var/log/mysql/mysql-slow.log';
SET GLOBAL long_query_time = 2;
SET GLOBAL log_queries_not_using_indexes = 'ON';

参数说明:

  • long_query_time:超过多少秒算慢查询。我一般设成 1 秒或 2 秒。
  • log_queries_not_using_indexes:记录没有使用索引的查询。这个很有用,能帮你发现“漏网之鱼”。
我的建议: 在开发环境或测试环境,可以把 long_query_time 设成 0,记录所有SQL。这样能全面了解应用的查询模式。但线上环境建议设成 1 或 2,避免日志量过大。

慢查询日志分析:从日志到优化

日志开启后,你会看到类似这样的内容:

# Time: 2025-01-15T10:30:00.123456Z
# User@Host: root[root] @ localhost []
# Query_time: 3.500724  Lock_time: 0.000182  Rows_sent: 100  Rows_examined: 500000
SET timestamp=1736922600;
SELECT * FROM orders WHERE status = 'pending' ORDER BY created_at DESC;

关键信息:

  • Query_time:查询耗时,3.5秒,明显是慢查询。
  • Rows_examined:扫描了 50 万行,但只返回了 100 行。说明扫描了大量无用数据。
  • Rows_sent:实际返回的行数。

拿到这条SQL后,我会做两件事:

  1. 用 EXPLAIN 分析执行计划,看看 type 和 Extra。
  2. 检查 status 和 created_at 是否有合适的索引。

我曾经遇到过一个问题,慢查询日志里全是同一个查询。分析后发现,status 字段的区分度太低(只有几个值),导致 MySQL 觉得用索引还不如全表扫描快。后来我改成了 (status, created_at) 的联合索引,查询速度提升了几十倍。

避坑指南: 慢查询日志文件会不断增长,记得配置日志轮转(log rotation),或者定期清理。不然磁盘满了,数据库可就出大事了。

知识体系总览

下面这张图,帮你把本章的核心逻辑串起来:

执行计划分析知识体系 EXPLAIN 命令 type 列解读 const → eq_ref → ref → range → index → ALL Extra 信息含义 Using index / Using temporary / Using filesort 慢查询日志 配置 → 采集 → 分析 → 优化 核心目标:通过执行计划定位慢查询,用索引优化提升性能

说白了,执行计划分析就是一条线:发现问题(慢查询日志)→ 定位问题(EXPLAIN)→ 解决问题(索引优化)。这三步走通了,你离SQL优化高手就不远了。