MySQL执行计划分析Explain
MySQL 是业务系统中最常见的数据库,但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发,讲常见问题和解决思路。
一、EXPLAIN基础用法
#
1.1 基本语法
EXPLAIN SELECT * FROM user WHERE id = 1; |
#
1.2 输出字段说明
| 字段 | 含义 |
|---|---|
| id | 查询标识符 |
| select_type | 查询类型 |
| table | 访问的表 |
| partitions | 匹配的分区 |
| type | 访问类型(重要) |
| possible_keys | 可能使用的索引 |
| key | 实际使用的索引 |
| key_len | 使用索引的长度 |
| ref | 与索引比较的列 |
| rows | 扫描的行数(估算) |
| filtered | 过滤后剩余比例 |
| Extra | 额外信息(重要) |
二、关键字段详解
#
2.1 id字段
标识SELECT的执行顺序:
EXPLAIN SELECT * FROM user WHERE id IN ( |
+----+-------------+---------+ |
- id相同:从上到下执行
- id不同:id大的先执行
- id有相同有不同:先执行id大的,相同的从上到下
#
2.2 select_type字段
| 类型 | 说明 |
|---|---|
| SIMPLE | 简单查询,无子查询或UNION |
| PRIMARY | 最外层查询 |
| SUBQUERY | SELECT中的子查询 |
| DERIVED | FROM中的子查询(派生表) |
| UNION | UNION中的第二个及后续查询 |
| UNION RESULT | UNION的结果合并 |
| DEPENDENT SUBQUERY | 依赖外部查询的子查询 |
| MATERIALIZED | 物化子查询(MySQL 5.6+) |
#
2.3 type字段(重点)
性能从好到差排序:
system > const > eq_ref > ref > range > index > ALL |
system:表只有一行,是const的特例
EXPLAIN SELECT * FROM mysql.proxies_priv; |
const:通过主键或唯一索引一次命中
EXPLAIN SELECT * FROM user WHERE id = 1; |
eq_ref:JOIN中通过主键或唯一索引关联,对于前表的每一行,后表只有一行匹配
EXPLAIN SELECT * FROM user u |
ref:使用非唯一索引
EXPLAIN SELECT * FROM user WHERE name = 'Alice'; |
range:索引范围扫描
EXPLAIN SELECT * FROM user WHERE id BETWEEN 1 AND 100; |
index:全索引扫描
EXPLAIN SELECT name FROM user; |
ALL:全表扫描
EXPLAIN SELECT * FROM user WHERE age > 0; |
优化目标:至少达到range级别,最好达到ref或更高。
#
2.4 key和key_len
key:实际使用的索引。NULL表示未使用索引。
key_len:索引使用的字节数。可用于判断是否使用了索引的全部列:
-- 联合索引(name, age),name是VARCHAR(20) |
常用类型的key_len计算:
| 数据类型 | key_len |
|———|———|
| INT | 4(非NULL)/ 5(可为NULL)|
| BIGINT | 8 / 9 |
| VARCHAR(n) | n * charset字节 + 2 |
| CHAR(n) | n * charset字节 |
| DATETIME | 5 / 6 |
#
2.5 rows字段
MySQL估算的扫描行数。这个值越小越好。
#
2.6 Extra字段(重点)
| 值 | 含义 | 是否优化 |
|---|---|---|
| Using index | 覆盖索引,无需回表 | 优 |
| Using where | 使用WHERE过滤 | 正常 |
| Using index condition | 索引条件下推 | 优 |
| Using filesort | 需要额外排序 | 差,需优化 |
| Using temporary | 使用临时表 | 差,需优化 |
| Using join buffer | 使用连接缓存 | 正常 |
| Impossible WHERE | WHERE条件永远为false | 正常 |
| Select tables optimized away | 优化器确定只返回一行 | 优 |
Using filesort示例与优化:
-- 无索引或索引不满足排序需求 |
Using temporary示例与优化:
-- GROUP BY + 排序导致临时表 |
三、EXPLAIN ANALYZE(MySQL 8.0.18+)
不仅显示计划,还显示实际执行时间:
EXPLAIN ANALYZE SELECT * FROM user WHERE id > 100 LIMIT 10; |
输出示例:
-> Limit: 10 row(s) (cost=11.25..11.35 rows=10) (actual time=0.089..0.095 rows=10 loops=1) |
关键信息:
- cost:预估成本
- actual time:实际耗时
- rows:实际返回/扫描行数
- loops:循环次数
四、实战案例分析
#
4.1 案例一:索引失效分析
EXPLAIN SELECT * FROM user |
输出:
+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+ |
问题诊断:
- type=ALL:全表扫描
- key=NULL:未使用索引
- 原因:函数操作导致索引失效
优化方案:
-- 改写为范围查询 |
#
4.2 案例二:隐式转换分析
-- phone字段是VARCHAR类型 |
输出:
+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+ |
问题诊断:
- possible_keys有idx_phone,但key=NULL
- 发生隐式类型转换,索引未使用
优化方案:
SELECT * FROM user WHERE phone = '13800138000'; |
#
4.3 案例三:ORDER BY优化
EXPLAIN SELECT * FROM user |
输出:
Extra: Using where; Using filesort |
优化方案:
-- 添加复合索引 |
#
4.4 案例四:JOIN优化
EXPLAIN SELECT u.name, o.amount |
输出:
+----+-------------+-------+--------+---------------+---------+---------+-----------------+--------+-------------+ |
问题诊断:
- orders表全表扫描(type=ALL)
- 原因:LEFT JOIN + WHERE条件在右表,导致优化器将LEFT JOIN转为INNER JOIN
优化方案:
-- 改为INNER JOIN |
五、SHOW WARNINGS
EXPLAIN后执行SHOW WARNINGS可以看到优化器改写后的SQL:
EXPLAIN SELECT * FROM user WHERE id = 1; |
输出:
Level: Note |
六、优化流程总结
#
6.1 EXPLAIN分析步骤
- 看type:是否达到range及以上,避免ALL
- 看key:是否使用了预期的索引
- 看rows:扫描行数是否合理
- 看Extra:是否存在Using filesort、Using temporary
- 看key_len:索引是否充分利用
#
6.2 优化决策树
EXPLAIN分析 |
七、常用优化技巧速查
| 问题 | EXPLAIN表现 | 解决方案 |
|---|---|---|
| 全表扫描 | type=ALL | 添加索引,改写SQL |
| 索引失效 | possible_keys有值但key=NULL | 避免函数、隐式转换、前导% |
| 文件排序 | Extra=Using filesort | 索引包含排序列 |
| 临时表 | Extra=Using temporary | 简化GROUP BY,添加索引 |
| 回表多 | 非覆盖索引 | 添加查询列到索引(覆盖索引) |
| JOIN性能差 | 驱动表rows大 | 确保关联列有索引,小表驱动 |
八、总结
EXPLAIN是SQL优化的”X光机”。掌握以下要点:
- 重点关注:type、key、rows、Extra四个字段
- 优化目标:type至少range,避免ALL;Extra避免filesort和temporary
- 结合实践:用EXPLAIN ANALYZE验证实际执行时间
- 持续监控:定期用慢查询日志发现需要优化的SQL
熟练使用EXPLAIN,能大幅提升SQL优化效率,快速定位性能瓶颈。
核心要点
索引设计的原则:最左前缀原则、避免索引列参与计算、选择合适的索引类型
SQL 优化的方法:使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN
事务隔离级别的选择:根据业务需求选择合适的级别
常见的锁问题:行锁、表锁、死锁的处理
总结
MySQL 优化是一个持续的过程,需要结合业务特点和数据量来调整。在实际项目中,定期分析慢查询日志、优化索引、调整配置参数,都能提升数据库性能。