作为MySQL性能调优的核心工具,EXPLAIN命令能帮助我们理解SQL语句的执行计划。本文将全面解析EXPLAIN输出的每个字段及其可能的值,并通过实际场景说明如何利用这些信息优化查询性能。
EXPLAIN SELECT * FROM users WHERE id = 1;
-- 或更详细的格式
EXPLAIN FORMAT=JSON SELECT * FROM users WHERE id = 1;
表示SELECT查询的序列号,有几个SELECT就有几个id。
重要特性:
值 | 描述 | 场景示例 |
---|---|---|
SIMPLE | 简单查询 | 不含子查询或UNION的查询 |
PRIMARY | 主查询 | 包含子查询的外层查询 |
SUBQUERY | 子查询 | SELECT列表或WHERE中的子查询 |
DERIVED | 派生表 | FROM子句中的子查询 |
UNION | UNION中的第二个或后面的查询 | SELECT...UNION SELECT... |
UNION RESULT | UNION的结果 | UNION操作的结果集 |
显示查询涉及的表名,可能是:
(派生表,N是id)
(UNION结果)显示查询将访问的分区,非分区表为NULL。
从最优到最差排序:
类型 | 描述 | 场景 |
---|---|---|
system | 系统表,只有一行 | 系统表查询 |
const | 通过主键或唯一索引查找 | WHERE id = 1 |
eq_ref | 唯一索引关联 | 多表JOIN使用主键或唯一键 |
ref | 非唯一索引查找 | 使用普通索引的查询 |
fulltext | 全文索引 | 使用FULLTEXT索引 |
ref_or_null | 类似ref,但包含NULL值 | WHERE col = ... OR col IS NULL |
index_merge | 索引合并 | 多个索引条件OR组合 |
unique_subquery | 子查询返回唯一值 | WHERE col IN (SELECT primary_key FROM ...) |
index_subquery | 类似unique_subquery但返回不唯一 | WHERE col IN (SELECT key FROM ...) |
range | 索引范围扫描 | BETWEEN , IN , > , < 等 |
index | 全索引扫描 | 只需扫描索引无需回表 |
ALL | 全表扫描 | 无合适索引或强制全表扫描 |
性能提示:至少达到range级别,避免ALL。
列出查询可能使用的索引,但不一定实际使用。
查询实际使用的索引,NULL表示未使用索引。
表示索引中使用的字节数,可用于判断使用了索引的哪些部分。
计算规则:
显示索引查找时使用了哪些列或常量。
常见值:
MySQL估计需要读取的行数(不是精确值)。
表示存储引擎返回的数据在服务层过滤后剩余的比例(0-100%)。
值 | 描述 | 优化建议 |
---|---|---|
Using index | 覆盖索引 | 良好,无需回表 |
Using where | 服务层过滤 | 可能需要优化索引 |
Using temporary | 使用临时表 | 考虑优化复杂GROUP BY |
Using filesort | 外部排序 | 需要优化ORDER BY |
Select tables optimized away | 优化器优化掉了表访问 | 极佳情况 |
Impossible WHERE | WHERE条件永远为假 | 检查查询条件 |
Distinct | 优化DISTINCT操作 | |
Using join buffer | 使用连接缓冲 | 可能需要优化JOIN |
EXPLAIN SELECT * FROM orders WHERE user_id = 100;
+----+-------------+--------+------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+---------------+---------+---------+-------+------+-------+
| 1 | SIMPLE | orders | ref | user_id | user_id | 4 | const | 5 | |
+----+-------------+--------+------+---------------+---------+---------+-------+------+-------+
分析:使用了user_id索引,类型为ref,预估扫描5行,性能良好。
EXPLAIN SELECT * FROM products WHERE category = 'electronics' ORDER BY price DESC;
可能输出:
+----+-------------+----------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+------+---------------+------+---------+------+------+----------------+
| 1 | SIMPLE | products | ALL | category | NULL | NULL | NULL | 5000 | Using filesort |
+----+-------------+----------+------+---------------+------+---------+------+------+----------------+
问题:全表扫描(ALL)和使用文件排序(Using filesort)。
优化方案:
ALTER TABLE products ADD INDEX (category, price);
EXPLAIN
SELECT u.name, COUNT(o.id)
FROM users u LEFT JOIN orders o ON u.id = o.user_id
WHERE u.create_time > '2023-01-01'
GROUP BY u.id
HAVING COUNT(o.id) > 5;
JSON格式输出分析要点:
{
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "1257.80"
},
"nested_loop": [
{
"table": {
"table_name": "u",
"access_type": "range",
"possible_keys": ["PRIMARY","create_time"],
"key": "create_time",
"rows_examined_per_scan": 342,
"rows_produced_per_join": 342,
"filtered": "100.00",
"using_index": true
}
},
{
"table": {
"table_name": "o",
"access_type": "ref",
"possible_keys": ["user_id"],
"key": "user_id",
"rows_examined_per_scan": 5,
"rows_produced_per_join": 1710,
"filtered": "100.00"
}
}
],
"grouping_operation": {
"using_temporary_table": true,
"using_filesort": true,
"cost_info": {
"sort_cost": "1710.00"
}
}
}
}
问题点:使用了临时表和文件排序。
优化方案:
通过key_len判断索引使用完整性:
EXPLAIN SELECT * FROM table WHERE col1 = 1 AND col2 = 'abc';
-- 如果key_len小于预期,说明未完全使用复合索引
观察多表JOIN时表的处理顺序,确保驱动表是小表。
比较rows和实际数据量,判断统计信息是否准确:
ANALYZE TABLE table_name; -- 更新统计信息
掌握EXPLAIN是MySQL性能优化的基本功。关键要点:
通过系统性地分析EXPLAIN结果,可以精准定位查询性能瓶颈,制定针对性的优化策略。