EXPLAIN 是 SQL 性能优化的关键工具,它展示了 MySQL 如何执行一条 SQL 语句。通过分析它的结果,你可以找出查询的瓶颈并进行优化。
下面我将对 EXPLAIN 进行详细解析。
一、什么是 EXPLAIN?
EXPLAIN 关键字可以放在 SELECT、DELETE、INSERT、REPLACE 或 UPDATE 语句之前,MySQL 会返回该语句的执行计划,而不是真正执行它。
执行计划揭示了 MySQL 优化器决定如何访问表、使用哪些索引、表之间的连接方式等信息。
基本语法:
sql
EXPLAIN your_sql_statement;
-- 例如
EXPLAIN SELECT * FROM users WHERE name = 'John';
在 MySQL 8.0 之后,推荐使用 EXPLAIN FORMAT=TRADITIONAL(默认格式),或者更详细的 EXPLAIN ANALYZE(MySQL 8.0.18+ 引入,会实际执行查询并给出更精确的分析)。
二、EXPLAIN 输出列详解
执行 EXPLAIN 后,你会得到一个包含多列的结果集。以下是这些列的含义,其中前几列(type, key, rows, Extra)最为重要。
| 列名 | 描述 | 重要性 |
|---|---|---|
| id | 查询中每个 SELECT 子句的唯一标识符。如果相同,则按顺序执行;如果不同,id 越大优先级越高(先执行)。 | 中 |
| select_type | 查询的类型,如 SIMPLE, PRIMARY, SUBQUERY, DERIVED 等。 | 中 |
| table | 正在访问的表名。 | 低 |
| partitions | 匹配的分区,非分区表则为 NULL。 | 低 |
| type | 连接类型 或 访问类型 。这是衡量查询性能的最关键指标之一。 | 高 |
| possible_keys | 查询中可能用到的索引。 | 中 |
| key | 查询中实际决定使用的索引。为 NULL 则表示未使用索引。 | 高 |
| key_len | 使用的索引键的长度。可用于判断是否充分利用了复合索引。 | 中 |
| ref | 显示索引的哪一列被使用了,通常是一个常量(const)或另一个表的列名。 | 中 |
| rows | MySQL 估计为了找到所需的行而需要读取的行数。这个值越小越好。 | 高 |
| filtered | 表示存储引擎返回的数据在服务器层过滤后,剩余多少比例满足查询条件。理想是 100。 | 中 |
| Extra | 包含不适合在其他列显示的额外信息,非常多的重要信息在这里。 | 高 |
三、核心列深度解析
1. type(访问类型)
性能从最优到最差排序如下:
-
system > const > eq_ref > ref > range > index > ALL
-
system:表只有一行记录(等于系统表),是 const 类型的特例。
-
const :通过索引一次就找到了,用于比较 主键索引 或 唯一索引 与常数值。速度极快。
sql
EXPLAIN SELECT * FROM users WHERE id = 1; -- `id` 是主键 -
eq_ref :在连接查询时,使用 主键 或 唯一非空索引 进行关联。对于来自前表的每一行,从本表中只返回一行。这是除了
system和const之外最好的连接类型。sql
EXPLAIN SELECT * FROM users u JOIN orders o ON u.id = o.user_id; -- `o.user_id` 是 `users` 表的主键 `id` 的外键,并且是唯一索引 -
ref :使用 非唯一性索引 进行扫描,返回匹配某个单独值的所有行。
sql
EXPLAIN SELECT * FROM users WHERE name = 'John'; -- `name` 字段上有一个普通索引(非唯一) -
range :只检索给定范围的行,使用一个索引来选择行。关键运算符是
BETWEEN、>、<、IN等。sql
EXPLAIN SELECT * FROM users WHERE id > 10; EXPLAIN SELECT * FROM users WHERE id IN (1, 2, 3); -
index :全索引扫描。遍历整个索引树来查找数据,比 ALL 快一点,因为索引文件通常比数据文件小。
sql
EXPLAIN SELECT id FROM users; -- 查询的列 `id` 正好是索引的一部分,直接从索引中读取,无需回表 -
ALL :全表扫描 。性能最差,意味着 MySQL 会遍历整张表来找到匹配的行。必须优化。
2. rows(预估行数)
这不是查询结果的行数,而是 MySQL 为了找到目标记录,预估需要扫描多少行。这是一个基于统计信息的预估值。这个值越小越好,说明查询效率高。
3. Extra(额外信息)
这里包含大量细节,是判断查询质量的另一个关键。
-
Using index :覆盖索引。查询的列都包含在索引中,无需回表查询数据行。性能极佳。
sql
-- 假设在 `name` 和 `age` 上有一个复合索引 (name, age) EXPLAIN SELECT name, age FROM users WHERE name = 'John'; -
Using where :表示在存储引擎返回行后,MySQL 服务器层再次进行了过滤。如果
type是ALL或index,并且出现Using where,通常意味着性能不佳。 -
Using temporary :MySQL 需要创建一张临时表来处理查询。常见于
GROUP BY和ORDER BY的子句涉及不同列时。需要优化。 -
Using filesort :MySQL 无法使用索引对结果进行排序,需要额外的排序步骤。如果数据量大,会非常消耗资源。需要优化。
-
Using join buffer:表示连接查询时,没有使用索引,需要用到连接缓冲区。通常意味着连接字段上没有索引。
四、实战分析示例
假设我们有两张表:
users 表
-
id(主键) -
name(有普通索引) -
email
orders 表
-
id(主键) -
user_id(外键,关联users.id,有索引) -
amount
查询1:简单的等值查询
sql
EXPLAIN SELECT * FROM users WHERE name = 'Alice';
可能的结果分析:
-
type: ref (使用了非唯一索引) -
key: name (实际使用了name索引) -
rows: 1 (预估扫描1行) -
Extra: (空,或者Using index如果查询的列都被索引覆盖)
结论: 这是一个高效的查询。
查询2:连接查询
sql
EXPLAIN SELECT u.name, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.name = 'Bob';
可能的结果分析(对于 orders 表这一行):
-
type: ref (使用user_id索引来关联) -
key: user_id -
rows: 5 (预估每个用户平均有5个订单) -
Extra: (空)
结论: 连接效率很高,因为双方都使用了索引。
查询3:性能不佳的查询
sql
EXPLAIN SELECT * FROM users WHERE email = 'alice@example.com' ORDER BY name;
可能的结果分析:
-
type: ALL (全表扫描,因为email字段没有索引) -
key: NULL -
rows: 10000 (表中有1万行数据) -
Extra: Using filesort (在内存或磁盘上进行排序)
结论: 这是一个灾难性的查询。它进行了全表扫描,并且还有一个昂贵的文件排序。
优化建议:
-
为
email字段添加索引。 -
如果经常需要按
name排序,可以考虑建立(email, name)的复合索引,这样可以利用索引来查找和排序。
五、进阶工具:EXPLAIN ANALYZE (MySQL 8.0.18+)
EXPLAIN ANALYZE 会实际执行查询,并提供一个更详细的、包含实际执行时间的分析报告。
sql
EXPLAIN ANALYZE SELECT * FROM users WHERE name = 'John';
输出格式类似于:
text
-> Index lookup on users using idx_name (name='John') (cost=0.35 rows=1) (actual time=0.025..0.027 rows=1 loops=1)
它提供了:
-
实际执行时间 (
actual time)。 -
实际返回行数 (
rows)。 -
执行循环次数 (
loops)。 -
预估成本 (
cost)。
这比传统的 EXPLAIN 提供了更精确的性能视图。
总结
| 检查点 | 目标 |
|---|---|
type 列 |
至少达到 range 级别,最好能到 ref。避免 ALL。 |
key 列 |
确保查询实际使用了合适的索引,不为 NULL。 |
rows 列 |
预估扫描行数尽可能小。 |
Extra 列 |
追求出现 Using index。警惕 Using temporary 和 Using filesort。 |
熟练掌握 EXPLAIN 是每个后端开发者和 DBA 的必备技能,它能帮助你从"猜测"优化变为"数据驱动"的优化。