MySQL EXPLAIN 执行计划全解析
MySQL的EXPLAIN命令是SQL优化和性能分析的核心工具,通过解析其输出的12个关键字段(包括id、select_type、type、key等)可以深入了解查询执行计划。其中type列(访问类型)尤为重要,按性能排序为system>const>eq_ref>ref>range>index>ALL,全表扫描(ALL)必须优化。其他关键指标如key(实际使用索
摘要:在数据库面试和生产事故排查中,
EXPLAIN是最基础也是最重要的工具。很多人只看type和key,这远远不够。本文将按照官方标准,对EXPLAIN输出的 12 个列(id, select_type, table, partitions, type, possible_keys, key, key_len, ref, rows, filtered, Extra)进行逐一深度解析,帮你彻底吃透 SQL 优化的底层逻辑。
一、 EXPLAIN 概览
不管你的 SQL 写得多么花哨,在 MySQL 看来,最终都要转化成一系列的底层操作。使用 EXPLAIN 关键字,我们可以看到 MySQL 优化器(Optimizer)是如何执行你的 SQL 的。
执行命令:
EXPLAIN SELECT * FROM user WHERE id = 1;
结果通常包含以下 12 个字段。我们将从左到右,抽丝剥茧。
二、 12 个核心字段深度解析
1. id (查询序列号)
含义:EXPLAIN 输出中的 id 列代表 SELECT 查询的序列号,它由一组数字组成,用于表示查询中执行 SELECT 子句或操作表的顺序。这个序列号可以帮助我们理解复杂查询的执行流程。
详细说明:
-
id 相同的情况:
- 当多个 SELECT 子句具有相同的 id 值时,表示这些子句是同一级别的查询
- 执行顺序遵循从上往下的原则
- 示例:
这种情况下,table1 和 table2 的 id 值相同,执行顺序取决于它们在 SQL 中的位置EXPLAIN SELECT * FROM table1, table2 WHERE table1.id = table2.id;
-
id 不同的情况:
- 当 id 值不同时,通常表示存在子查询或嵌套查询
- id 值会随着查询的嵌套层级递增
- 执行原则:id 值越大优先级越高,该查询会越先被执行
- 示例:
这里子查询的 id 值会比主查询大EXPLAIN SELECT * FROM table1 WHERE id IN (SELECT id FROM table2);
-
id 为 NULL 的情况:
- 这种特殊情况通常出现在 UNION 操作的结果集中
- 表示该行是 UNION 结果的合并操作
- 在查询执行顺序中,这类操作通常是最后执行的
- 示例:
UNION 结果的合并行会显示 id 为 NULLEXPLAIN SELECT * FROM table1 UNION SELECT * FROM table2;
应用场景:
- 分析复杂查询的执行计划时,通过 id 列可以快速识别查询的层级关系
- 优化查询性能时,了解执行顺序有助于发现潜在的性能瓶颈
- 在包含多个子查询或 JOIN 操作的复杂 SQL 中,id 列是理解执行流程的关键指标
2. select_type (查询类型)
含义:用来分辨查询的类型,主要是用于区别普通查询、联合查询、子查询等复杂查询。这个字段对于理解查询执行过程和优化SQL语句非常重要。
| 类型 | 描述 | 示例场景 |
|---|---|---|
| SIMPLE | 简单的 SELECT 查询,不包含子查询或 UNION。 | SELECT * FROM users WHERE id = 1; |
| PRIMARY | 查询中若包含任何复杂的子部分,最外层查询则被标记为 PRIMARY。 | 在包含子查询的语句中,外层查询会被标记为PRIMARY |
| SUBQUERY | 在 SELECT 或 WHERE 列表中包含了子查询。 | SELECT * FROM products WHERE price > (SELECT AVG(price) FROM products); |
| DERIVED | 在 FROM 列表中包含的子查询被标记为 DERIVED(衍生),MySQL 会递归执行这些子查询,把结果放在临时表里。 | SELECT * FROM (SELECT * FROM orders WHERE status='paid') AS temp_table; |
| UNION | 若第二个 SELECT 出现在 UNION 之后,则被标记为 UNION。 | SELECT id FROM users UNION SELECT id FROM customers; |
| UNION RESULT | 从 UNION 表获取结果的 SELECT。 | 在执行UNION查询时,合并结果的查询会被标记为UNION RESULT |
补充说明:
- 对于复杂查询,MySQL会先执行子查询(SUBQUERY或DERIVED),然后将结果保存在临时表中
- UNION RESULT通常出现在EXPLAIN结果的最后一行,表示合并UNION操作的结果
- DERIVED子查询会创建临时表,这可能影响查询性能,在优化时需要考虑
3. table (表名)
含义:显示这一行的数据是关于哪张表的。
- 如果是子查询或衍生表,这里可能会显示
<derived2>,表示该结果来自于 id 为 2 的查询结果。
4. partitions (分区)
含义:匹配的分区信息。
- 如果表没有分区,通常为
NULL。 - 对于分区表,显示查询将访问的分区名称。这在优化分区表的查询时非常关键。
5. type (访问类型) —— ⭐ 核心指标
含义:显示查询使用了何种类型。这是衡量 SQL 性能好坏最直观的指标,通过分析访问类型可以快速判断查询是否需要优化。
性能从优到差排序:system > const > eq_ref > ref > range > index > ALL
详细说明及示例:
-
system:表只有一行记录(系统表),这是 const 类型的特例。例如:
SELECT * FROM mysql.proc WHERE name='procedure_name'; -
const:通过索引一次就找到了,const 用于比较 primary key 或 unique 索引。例如:
SELECT * FROM users WHERE user_id = 1; -- user_id是主键 -
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描。例如:
SELECT * FROM users JOIN orders ON users.user_id = orders.user_id; -- orders.user_id是唯一索引 -
ref:非唯一性索引扫描,返回匹配某个单独值的所有行。例如:
SELECT * FROM users WHERE username = 'john'; -- username是普通索引 -
range:只检索给定范围的行,使用一个索引来选择行(如
between,>,<,in)。这是 SQL 优化的底线。例如:SELECT * FROM orders WHERE order_date BETWEEN '2023-01-01' AND '2023-01-31'; -
index:Full Index Scan,全索引扫描。只遍历索引树,通常比 ALL 快,因为索引文件通常比数据文件小。例如:
SELECT username FROM users; -- username列有索引 -
ALL:Full Table Scan,全表扫描。必须优化。例如:
SELECT * FROM users WHERE last_name LIKE '%son%'; -- 没有合适的索引可用
6. possible_keys (可能用到的索引)
含义:显示可能应用在这张表中的索引,一个或多个。
- 查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。
7. key (实际使用的索引) —— ⭐ 核心指标
含义:实际使用的索引。
- 如果为
NULL,则没有使用索引。 - 覆盖索引提示:如果查询中使用了覆盖索引(Covering Index),则该索引仅出现在
key列表中,可能不出现在possible_keys中。
8. key_len (索引长度)
含义:表示索引中使用的字节数。
- 可通过该列计算查询中使用的索引的长度。
- 作用:在联合索引中,
key_len可以帮你判断索引是否被完全使用,还是只用了前缀部分。 - 例如:联合索引 (a, b, c),如果 key_len 长度只对应 a 的长度,说明 b 和 c 没用到。
9. ref (索引比较对象)
含义:在 EXPLAIN 输出的 ref 列中,显示的是索引查找时使用的比较对象,即哪些列或常量被用于与索引列进行值匹配。如果可能的话,这个比较对象会显示为一个常数(const)。
详细说明:
-
const 情况:当查询条件使用常量值直接与索引列比较时,ref 会显示为 “const”。例如:
EXPLAIN SELECT * FROM users WHERE id = 10;这里 id 是主键索引,与常量值 10 比较,ref 列会显示 “const”。
-
列引用情况:当使用其他表的列作为比较对象时,会显示该列名。例如在多表连接时:
EXPLAIN SELECT * FROM orders JOIN customers ON orders.customer_id = customers.id;如果 orders.customer_id 使用了 customers.id 列的值进行索引查找,ref 会显示 “customers.id”。
-
func 情况:当使用函数处理后的结果进行索引匹配时,会显示 “func”。例如:
EXPLAIN SELECT * FROM users WHERE MONTH(created_at) = 5;这里对 created_at 使用了 MONTH() 函数,ref 会显示 “func”。
10. rows (预估行数) —— ⭐ 核心指标
含义:MySQL 根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数。
- 越少越好。注意,这不是结果集的大小,而是扫描的大小。
11. filtered (过滤比例)
含义:表示返回结果的行数占开始查找行数的百分比。
- 值越大越好。
- 例如:
rows = 1000,filtered = 50.00,说明 MySQL 扫描了 1000 行,但最后只有 50%(500行)是符合 WHERE 条件的。这暗示索引可能不够精准。
12. Extra (额外信息) —— ⭐ 核心指标
含义:包含不适合在其他列中显示但十分重要的额外信息。
出现以下情况需重点关注:
- ✅ Using index:好! 表示相应的 SELECT 操作中使用了覆盖索引,避免访问表的数据行(回表),效率不错。
- ❌ Using filesort:坏! 说明 MySQL 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。需优化索引。
- ❌ Using temporary:极坏! 使用了临时表保存中间结果,常见于
ORDER BY和GROUP BY。 - ℹ️ Using where:表明使用了 WHERE 过滤。
- ℹ️ Using join buffer:使用了连接缓存,说明 JOIN 效率可能不高,需要检查关联字段的索引。
三、 实战演练:如何看懂这 12 个字段?
假设有如下 SQL:
EXPLAIN SELECT * FROM order_info WHERE user_id = 1001 ORDER BY create_time;
模拟输出分析:
| id | select_type | table | type | key | key_len | Extra |
|---|---|---|---|---|---|---|
| 1 | SIMPLE | order_info | ref | idx_userid | 4 | Using index condition; Using filesort |
深度解读:
- id=1, select_type=SIMPLE:这是一个简单查询。
- type=ref:使用了非唯一索引扫描,性能尚可。
- key=idx_userid:实际使用了
user_id的索引。 - Extra=Using filesort:这是性能痛点!
- 虽然
user_id走了索引,但ORDER BY create_time并没有利用到索引,导致 MySQL 进行了文件排序。 - 优化建议:建立联合索引
idx_userid_createtime (user_id, create_time),利用索引的有序性消除filesort。
四、 总结
EXPLAIN 是 MySQL 调优的入场券。请务必记住以下 3 条黄金法则:
- **紧盯
type**:努力优化到ref或range,坚决消灭ALL。 - **关注
Extra**:出现Using filesort或Using temporary时,意味着你的索引设计有问题。 - **计算
key_len**:在使用联合索引时,确保最左前缀法则生效,让索引“用满”。
更多推荐
所有评论(0)