在MySQL性能优化中,EXPLAIN命令是每个开发者都必须掌握的。它就像一台SQL查询的X光机,让我们能透视查询的内部执行机制,找出性能瓶颈的根源。
目录
-
-
[1. id:查询标识符](#1. id:查询标识符)
-
[2. select_type:查询类型](#2. select_type:查询类型)
-
[3. table:访问的表](#3. table:访问的表)
-
[4. partitions:匹配的分区](#4. partitions:匹配的分区)
-
[5. type:连接类型(关键指标)](#5. type:连接类型(关键指标))
-
[6. possible_keys:可能使用的索引](#6. possible_keys:可能使用的索引)
-
[7. key:实际使用的索引](#7. key:实际使用的索引)
-
[8. key_len:索引使用的字节数](#8. key_len:索引使用的字节数)
-
[9. ref:索引的哪一列被使用](#9. ref:索引的哪一列被使用)
-
[10. rows:预估需要扫描的行数](#10. rows:预估需要扫描的行数)
-
[11. filtered:过滤百分比](#11. filtered:过滤百分比)
-
[12. Extra:额外信息(重要)](#12. Extra:额外信息(重要))
-
为什么要使用EXPLAIN?
在数据库性能优化中,盲目调优往往是事倍功半。EXPLAIN命令能够展示MySQL如何执行一条SELECT语句,包括:
-
查询使用了哪些索引
-
表的读取顺序如何
-
数据是如何被检索和连接的
-
需要扫描多少行数据
掌握这些信息,我们才能有针对性地进行索引优化、查询重构和配置调整。
EXPLAIN基础使用
sql
-- 最基本的用法
EXPLAIN SELECT * FROM users WHERE age > 25;
-- 也可以写作
EXPLAIN FORMAT=TRADITIONAL SELECT * FROM users WHERE age > 25;
-- JSON格式输出,包含更详细信息
EXPLAIN FORMAT=JSON SELECT * FROM users WHERE age > 25;
深入解析EXPLAIN输出字段
1. id:查询标识符
-
作用:表示SELECT查询的序列号
-
解读规则:
-
id相同:执行顺序从上到下
-
id不同:数值越大优先级越高,越先执行
-
id为NULL:表示是结果集,常见于UNION查询
-
sql
EXPLAIN
SELECT u.name, o.order_no
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active';
2. select_type:查询类型
-
SIMPLE:简单查询,不使用UNION或子查询
-
PRIMARY:查询中最外层的SELECT
-
SUBQUERY:子查询中的第一个SELECT
-
DERIVED:派生表(FROM子句中的子查询)
-
UNION:UNION中的第二个及后续查询
-
UNION RESULT:UNION的结果集
3. table:访问的表
-
显示查询正在访问哪张表
-
如果是派生表,会显示
<derivedN>,其中N是id值 -
如果是UNION结果,会显示
<unionM,N>
4. partitions:匹配的分区
-
对于分区表,显示查询将访问哪些分区
-
非分区表则显示NULL
5. type:连接类型(关键指标)
这是判断查询效率的重要指标,性能从好到坏依次为:
| 类型 | 描述 | 性能等级 |
|---|---|---|
| system | 表只有一行记录(系统表) | ★★★★★ |
| const | 通过索引一次就找到,用于primary key或unique key的等值查询 | ★★★★★ |
| eq_ref | 唯一索引扫描,通常出现在多表join中 | ★★★★☆ |
| ref | 非唯一索引扫描,返回匹配某个值的所有行 | ★★★☆☆ |
| range | 索引范围扫描,常见于BETWEEN、>、<等操作 | ★★☆☆☆ |
| index | 全索引扫描,遍历整个索引树 | ★☆☆☆☆ |
| ALL | 全表扫描,性能最差,需要优化 | ☆☆☆☆☆ |
sql
-- const示例
EXPLAIN SELECT * FROM users WHERE id = 1;
-- ref示例
EXPLAIN SELECT * FROM users WHERE status = 'active';
-- range示例
EXPLAIN SELECT * FROM users WHERE age BETWEEN 20 AND 30;
-- index示例
EXPLAIN SELECT id FROM users; -- id是主键
6. possible_keys:可能使用的索引
-
显示查询可能用到的所有索引
-
如果为NULL,表示没有可能的索引
7. key:实际使用的索引
-
查询实际使用的索引
-
如果为NULL,表示没有使用索引
-
可能与possible_keys中的索引不同,MySQL会选择它认为最优的索引
8. key_len:索引使用的字节数
-
显示索引中使用的字节数
-
可以判断是否使用了索引的全部部分
-
计算规则:
-
字符串类型:字符集字节数 × 字段长度 + 是否NULL(1) + 变长字段长度(2)
-
数值类型:类型固定字节数
-
时间类型:类型固定字节数
-
sql
-- 假设name字段是varchar(100) utf8mb4
EXPLAIN SELECT * FROM users WHERE name = '张三';
-- key_len可能是 4*100+1+2 = 403
9. ref:索引的哪一列被使用
-
显示索引的哪一列被使用了
-
格式:数据库名.表名.列名 或 const
sql
EXPLAIN SELECT * FROM users u
JOIN orders o ON u.id = o.user_id;
-- 在orders表的执行计划中,ref可能是test.users.id
10. rows:预估需要扫描的行数
-
MySQL预估需要读取的行数
-
数值越小越好
-
注意:这是预估行数,不是实际行数
11. filtered:过滤百分比
-
表示存储引擎返回的数据在服务器层过滤后,剩余多少百分比
-
取值范围0-100,100表示没有过滤
-
在MySQL 5.7及以上版本可用
12. Extra:额外信息(重要)
包含MySQL解决查询的额外信息:
| 值 | 含义 | 是否需要优化 |
|---|---|---|
| Using index | 使用了覆盖索引 | ✓ 良好 |
| Using where | 在存储引擎检索行后进行了过滤 | △ 视情况 |
| Using temporary | 使用了临时表,常见于GROUP BY、ORDER BY | ✗ 需要优化 |
| Using filesort | 使用了文件排序 | ✗ 需要优化 |
| Using join buffer | 使用了连接缓存 | △ 视情况 |
| Impossible WHERE | WHERE条件永远为false | ✓ 良好 |
| Select tables optimized away | 使用聚合函数访问索引 | ✓ 良好 |
sql
-- Using index示例(覆盖索引)
EXPLAIN SELECT id, name FROM users WHERE name LIKE '张%';
-- 如果name有索引,且查询字段都在索引中
实战分析:一个完整示例
sql
-- 创建测试表
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
age INT,
status VARCHAR(20),
created_at DATETIME,
INDEX idx_age_status (age, status),
INDEX idx_name (name)
);
-- 分析复杂查询
EXPLAIN
SELECT u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.age > 25
AND u.status = 'active'
AND u.name LIKE '张%'
GROUP BY u.id
HAVING order_count > 0
ORDER BY u.created_at DESC
LIMIT 10;
执行计划解读:
-
id相同,按顺序执行users表到orders表的连接
-
type可能是range(age > 25)或ref(使用索引)
-
key可能是idx_age_status索引
-
Extra可能有Using where、Using temporary、Using filesort等
优化建议
-
关注type字段:尽量避免ALL和index,争取达到const、eq_ref、ref级别
-
留意Extra字段:
-
出现Using filesort:考虑优化ORDER BY
-
出现Using temporary:考虑优化GROUP BY或JOIN
-
争取出现Using index:使用覆盖索引
-
-
合理使用索引:
-
确保WHERE、JOIN、ORDER BY、GROUP BY涉及的列有索引
-
注意最左前缀原则
-
避免索引失效的场景(如函数操作、类型转换)
-
-
分析rows字段:
- 如果rows值远大于实际返回行数,可能需要更新统计信息
ANALYZE TABLE users;
高级技巧
1. 使用EXPLAIN ANALYZE(MySQL 8.0+)
sql
EXPLAIN ANALYZE
SELECT * FROM users WHERE age > 25;
-- 提供实际执行时间和预估的对比
2. 查看优化器跟踪
sql
SET optimizer_trace="enabled=on";
SELECT * FROM users WHERE age > 25;
SELECT * FROM information_schema.optimizer_trace;
SET optimizer_trace="enabled=off";
3. 对比不同写法的执行计划
sql
-- 写法1
EXPLAIN SELECT * FROM users WHERE age = 25 OR status = 'active';
-- 写法2
EXPLAIN SELECT * FROM users WHERE age = 25
UNION
SELECT * FROM users WHERE status = 'active';
常见问题排查
1. 为什么没走索引?
-
检查数据类型是否一致
-
检查是否使用了函数或计算
-
检查索引选择性(基数)
2. 如何查看索引基数?
sql
SHOW INDEX FROM users;
-- 关注Cardinality列
3. 为什么rows预估不准?
sql
-- 更新统计信息
ANALYZE TABLE users;
-- 强制使用索引(谨慎使用)
EXPLAIN SELECT * FROM users FORCE INDEX(idx_name) WHERE name LIKE '张%';
总结
EXPLAIN是MySQL性能优化不可或缺的工具。掌握每个字段的含义,就像拥有了一张SQL查询的详细地图。记住优化是一个迭代过程:
-
分析当前执行计划
-
识别瓶颈点(全表扫描、临时表、文件排序等)
-
实施优化措施(调整索引、重写查询等)
-
验证优化效果
-
重复此过程
通过持续监控和优化,你可以确保数据库查询始终保持在最佳性能状态。
最后:在真实生产环境中,一定要在测试环境验证EXPLAIN结果,并使用真实数据量进行测试,因为小数据量和大数据量的执行计划可能会有显著差异。