执行计划解读
执行计划通过EXPLAIN命令获取,展示SQL的执行路径和资源消耗。
EXPLAIN基本用法
查看执行计划
SQL
-- 基本语法
EXPLAIN SELECT ...;
-- MySQL 8.0+ 可使用EXPLAIN ANALYZE(实际执行)
EXPLAIN ANALYZE SELECT ...;
-- 格式化输出(JSON格式)
EXPLAIN FORMAT=JSON SELECT ...;
执行计划输出
SQL
EXPLAIN SELECT * FROM orders WHERE user_id = 1;
+----+-------------+--------+------+---------------+----------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+---------------+----------+---------+-------+------+-------+
| 1 | SIMPLE | orders | ref | idx_user | idx_user | 5 | const| 100 | NULL |
+----+-------------+--------+------+---------------+----------+---------+-------+------+-------+
id列
查询标识
SQL
id相同:同一级别,从上到下执行
id不同:子查询,id越大优先执行
id为NULL:结果集合并(UNION)
示例:
id=1: 主查询
id=2: 子查询(先执行)
id=NULL: UNION结果
select_type列
查询类型
| 类型 | 说明 | 性能影响 |
|---|---|---|
| SIMPLE | 简单查询,无子查询/UNION | ✅ 最佳 |
| PRIMARY | 外层查询 | 正常 |
| SUBQUERY | 子查询 | ⚠️ 可能慢 |
| DERIVED | 派生表(FROM子查询) | ⚠️ 临时表 |
| UNION | UNION第二个查询 | 正常 |
| UNION RESULT | UNION结果 | 正常 |
| DEPENDENT SUBQUERY | 依赖外层的子查询 | ❌ 性能差 |
table列
访问的表名
SQL
EXPLAIN SELECT * FROM orders o JOIN users u ON o.user_id = u.id;
-- table列显示访问的表
-- <derived N>:派生表别名
-- <union M,N>:UNION合并结果
type列(关键)
访问类型(从优到差)
| 类型 | 说明 | 性能 |
|---|---|---|
| system | 单行表(系统表) | ✅ 最优 |
| const | 单行匹配(主键/唯一索引) | ✅ 最优 |
| eq_ref | 唯一索引关联 | ✅ 优 |
| ref | 非唯一索引匹配 | ✅ 好 |
| range | 索引范围扫描 | ✅ 可接受 |
| index | 全索引扫描 | ⚠️ 较慢 |
| ALL | 全表扫描 | ❌ 最差 |
类型示例
SQL
-- const:主键或唯一索引单值匹配
EXPLAIN SELECT * FROM orders WHERE id = 1;
-- type: const
-- ref:非唯一索引匹配
EXPLAIN SELECT * FROM orders WHERE user_id = 1;
-- type: ref
-- range:范围查询
EXPLAIN SELECT * FROM orders WHERE id BETWEEN 1 AND 100;
-- type: range
-- ALL:全表扫描(需优化)
EXPLAIN SELECT * FROM orders WHERE amount + 1 = 100;
-- type: ALL
possible_keys列
可能使用的索引
SQL
EXPLAIN SELECT * FROM orders WHERE user_id = 1 AND status = 2;
-- possible_keys: idx_user,idx_user_status
-- 查询条件涉及的两个索引都可能出现
-- 最终使用哪个由key列决定
key列
实际使用的索引
SQL
EXPLAIN SELECT * FROM orders WHERE user_id = 1;
-- key: idx_user(实际使用的索引)
-- key: NULL(未使用索引,全表扫描)
-- key: PRIMARY(使用主键索引)
key_len列
索引使用的字节数
SQL
-- 计算规则:
-- 列类型字节 + 是否NULL(1字节) + 变长列长度(2字节)
CREATE TABLE t1 (
id INT PRIMARY KEY, -- key_len: 4
name VARCHAR(50), -- key_len: 50*3+2+1=153(utf8mb4)
status TINYINT NOT NULL, -- key_len: 1
INDEX idx_name(name),
INDEX idx_status(status)
);
EXPLAIN SELECT * FROM t1 WHERE name = 'test';
-- key_len: 153
EXPLAIN SELECT * FROM t1 WHERE status = 1;
-- key_len: 1
-- 联合索引使用长度判断使用了多少列
CREATE INDEX idx_name_status ON t1(name, status);
EXPLAIN SELECT * FROM t1 WHERE name = 'test' AND status = 1;
-- key_len: 153 + 1 = 154(全部使用)
EXPLAIN SELECT * FROM t1 WHERE name = 'test';
-- key_len: 153(只使用name列)
ref列
索引匹配来源
SQL
-- const:常量值
EXPLAIN SELECT * FROM orders WHERE user_id = 1;
-- ref: const
-- 列名:另一表的列
EXPLAIN SELECT * FROM orders o JOIN users u ON o.user_id = u.id;
-- ref: u.id
-- func:函数结果
EXPLAIN SELECT * FROM orders WHERE user_id = ABS(1);
-- ref: func
rows列
预估扫描行数
SQL
EXPLAIN SELECT * FROM orders WHERE user_id = 1;
-- rows: 100(预估扫描100行)
-- 非精确值,InnoDB使用估算
-- rows越大,查询成本越高
-- 关注 rows * type 的组合判断性能
filtered列
过滤比例
SQL
-- MySQL 5.7+ EXPLAIN EXTENDED(或FORMAT=JSON)显示filtered
EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE user_id = 1 AND status = 2;
-- filtered: 10.00(10%的记录满足status条件)
-- 索引扫描后WHERE过滤的比例
-- filtered较低说明还需大量过滤,可考虑扩展索引
Extra列(关键)
额外执行信息
| Extra值 | 说明 | 性能影响 |
|---|---|---|
| NULL | 正常执行 | ✅ 正常 |
| Using index | 覆盖索引 | ✅ 好 |
| Using where | WHERE过滤 | 正常 |
| Using index condition | 索引下推(ICP) | ✅ 好 |
| Using temporary | 使用临时表 | ⚠️ 可能慢 |
| Using filesort | 文件排序 | ⚠️ 可能慢 |
| Using join buffer | JOIN使用缓冲 | ⚠️ 大表关联 |
| Impossible WHERE | WHERE条件不可能 | 结果为空 |
| Distinct | DISTINCT优化 | 正常 |
重要Extra分析
SQL
-- Using index:覆盖索引,无需回表
EXPLAIN SELECT user_id FROM orders WHERE user_id = 1;
-- Extra: Using index
-- Using index condition:索引下推
EXPLAIN SELECT * FROM orders WHERE user_id = 1 AND status LIKE '2%';
-- Extra: Using index condition
-- Using temporary:使用临时表(GROUP BY可能触发)
EXPLAIN SELECT user_id, COUNT(*) FROM orders GROUP BY status;
-- Extra: Using temporary
-- Using filesort:文件排序(ORDER BY可能触发)
EXPLAIN SELECT * FROM orders ORDER BY amount;
-- Extra: Using filesort(amount无索引)
综合分析示例
text
-- 查询语句
SELECT user_id, status FROM orders WHERE user_id = 1 ORDER BY status;
-- 创建索引
CREATE INDEX idx_user_status ON orders(user_id, status);
-- 执行计划
EXPLAIN SELECT user_id, status FROM orders WHERE user_id = 1 ORDER BY status;
-- type: ref
-- key: idx_user_status
-- rows: 100
-- Extra: Using index
-- 分析:
-- 1. 使用索引 idx_user_status
-- 2. 覆盖索引,无需回表
-- 3. ORDER BY使用索引排序,无filesort
-- ✅ 优化成功
性能判断标准
text
好的执行计划特征:
- type ≤ ref(避免ALL)
- key有值(使用索引)
- rows较小(扫描少)
- Extra有Using index(覆盖)
需要优化:
- type = ALL(全表扫描)
- rows很大(大量扫描)
- Extra有Using temporary/filesort
要点总结
- type列决定访问效率,const最优,ALL最差
- key列显示实际使用的索引
- rows列预估扫描行数,值越小越好
- Extra列提供优化信息,Using index表示覆盖索引
- Using temporary/filesort表示需进一步优化
- 综合分析各列判断查询性能
📝 发现内容有误?点击此处直接编辑