mysql 性能优化之Explain讲解
EXPLAIN
是 MySQL 中用于分析查询执行计划的重要工具,通过它可以查看查询如何使用索引、扫描数据的方式以及表连接顺序等信息,从而找出性能瓶颈。以下是关于EXPLAIN
的详细介绍和实战指南:
1. EXPLAIN 基本用法
在SELECT
、INSERT
、UPDATE
、DELETE
语句前加上EXPLAIN
关键字即可查看执行计划:
EXPLAIN SELECT * FROM users WHERE age > 18;
2. 关键字段解析
EXPLAIN
返回的结果包含多个字段,重点关注以下几个:
id
- 查询的标识符,数值越大优先级越高,相同数值按顺序执行。
type
- 数据访问类型,从最优到最差排序:
system
/const
:单条记录查询(主键或唯一索引)。eq_ref
:唯一索引扫描(如JOIN
操作)。ref
:非唯一索引扫描。range
:范围扫描(如WHERE age > 18
)。index
:全索引扫描(仅扫描索引树)。ALL
:全表扫描(性能最差)。
possible_keys
- 可能使用的索引,但不一定实际使用。
key
- 实际使用的索引,若为
NULL
则未使用索引。
key_len
- 索引使用的字节数,用于评估索引的选择性。
rows
- MySQL 估算的扫描行数,值越小越好。
Extra
- 额外信息,常见值:
Using filesort
:需额外排序(性能开销大)。Using temporary
:使用临时表(如GROUP BY
或ORDER BY
)。Using index
:覆盖索引(仅通过索引即可获取所有数据)。
3. 实战优化案例
案例 1:全表扫描优化
问题 SQL:
SELECT * FROM orders WHERE status = 'paid';
EXPLAIN 结果:
+----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
| 1 | SIMPLE | orders | ALL | NULL | NULL | NULL | NULL | 100000 | Using where |
+----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
分析:type=ALL
(全表扫描),key=NULL
(未使用索引)。
优化:
ALTER TABLE orders ADD INDEX idx_status (status);
优化后 EXPLAIN:
+----+-------------+--------+------+---------------+----------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+---------------+----------+---------+-------+------+-------------+
| 1 | SIMPLE | orders | ref | idx_status | idx_status | 152 | const | 500 | Using index |
+----+-------------+--------+------+---------------+----------+---------+-------+------+-------------+
结果:type=ref
(索引扫描),rows=500
(扫描行数大幅减少),Using index
(覆盖索引)。
案例 2:复合索引优化
问题 SQL:
SELECT user_id, amount FROM orders WHERE user_id = 100 AND status = 'paid';
EXPLAIN 结果:
+----+-------------+--------+------+---------------+----------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+---------------+----------+---------+-------+------+-------------+
| 1 | SIMPLE | orders | ref | idx_user | idx_user | 4 | const | 1000 | Using where |
+----+-------------+--------+------+---------------+----------+---------+-------+------+-------------+
分析:仅使用了user_id
索引,未使用status
条件。
优化:创建复合索引:
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
优化后 EXPLAIN:
+----+-------------+--------+------+-------------------+-------------------+---------+-------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+-------------------+-------------------+---------+-------------+------+-------------+
| 1 | SIMPLE | orders | ref | idx_user_status | idx_user_status | 156 | const,const | 50 | Using index |
+----+-------------+--------+------+-------------------+-------------------+---------+-------------+------+-------------+
结果:rows=50
(扫描行数进一步减少),Using index
(覆盖索引)。
案例 3:消除Using filesort
问题 SQL:
SELECT * FROM products ORDER BY create_time LIMIT 10;
EXPLAIN 结果:
+----+-------------+----------+------+---------------+------+---------+------+--------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+------+---------------+------+---------+------+--------+----------------+
| 1 | SIMPLE | products | ALL | NULL | NULL | NULL | NULL | 100000 | Using filesort |
+----+-------------+----------+------+---------------+------+---------+------+--------+----------------+
分析:全表扫描后进行文件排序(Using filesort
)。
优化:添加索引:
ALTER TABLE products ADD INDEX idx_create_time (create_time);
优化后 EXPLAIN:
+----+-------------+----------+------+---------------+-------------------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+------+---------------+-------------------+---------+------+------+-------------+
| 1 | SIMPLE | products | index| NULL | idx_create_time | 5 | NULL | 10 | Using index |
+----+-------------+----------+------+---------------+-------------------+---------+------+------+-------------+
结果:type=index
(索引扫描),消除了Using filesort
。
4. 高级用法:EXPLAIN ANALYZE
MySQL 8.0+ 支持EXPLAIN ANALYZE
,返回更详细的执行信息,包括实际扫描行数和时间:
EXPLAIN ANALYZE SELECT * FROM users WHERE age > 18;
5. 优化建议
- 优先优化
type
字段:尽量避免ALL
和index
类型,目标是ref
或更优。 - 确保
key
字段非NULL
:通过创建索引让查询使用索引。 - 消除
Using filesort
和Using temporary
:通过合理索引避免额外排序和临时表。 - 利用覆盖索引:让
Extra
字段出现Using index
,减少回表操作。 - 复合索引顺序:将选择性高的字段放在前面(如唯一值多的字段)。
6. 常见误区
- 索引越多越好:过多索引会增加写操作开销和内存占用。
- 忽视复合索引顺序:不满足最左匹配原则会导致索引失效。
- 过度依赖
EXPLAIN
估算:rows
是估算值,实际可能有偏差,需结合SHOW PROFILE
等工具验证。
通过EXPLAIN
深入分析查询执行计划,针对性地优化索引和查询语句,可以显著提升 MySQL 性能。