MySQL EXPLAIN 使用详解与执行计划分析优化
MySQL EXPLAIN 使用详解与执行计划分析优化
一、什么是 EXPLAIN?
EXPLAIN
是 MySQL 提供的 SQL 语句分析工具,可以显示 SQL 语句在执行时的执行计划,包括表的访问顺序、使用的索引、连接类型、扫描行数等。通过分析 EXPLAIN 的输出结果,可以帮助我们发现 SQL 性能瓶颈,进行有针对性的优化。
二、EXPLAIN 的基本用法
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';
或者
EXPLAIN FORMAT=JSON SELECT * FROM users WHERE email = 'test@example.com';
三、EXPLAIN 输出字段详解
字段 | 含义 |
---|---|
id | 查询中每个 SELECT 子句的标识符,越大优先级越高 |
select_type | 查询类型(SIMPLE、PRIMARY、SUBQUERY、UNION等) |
table | 当前访问的表名或别名 |
partitions | 匹配的分区(如有分区表) |
type | 连接类型,反映表的访问方式(性能由好到差:system > const > eq_ref > ref > range > index > ALL) |
possible_keys | 可能用到的索引 |
key | 实际使用的索引 |
key_len | 使用索引的长度 |
ref | 哪个字段或常量与 key 一起使用 |
rows | 预估需要读取的行数 |
filtered | 经过条件过滤后剩余的百分比(MySQL 5.7+) |
Extra | 额外信息,如 Using index、Using where、Using filesort、Using temporary 等 |
四、type 连接类型说明(性能由好到差)
- system/const:表只有一行数据或主键等值查询,效率最高
- eq_ref:唯一索引等值查询
- ref:非唯一索引等值查询
- range:范围查询(如 between、>、<、in)
- index:全索引扫描
- ALL:全表扫描,性能最差
优化目标:
尽量让 type 为 const
、eq_ref
、ref
或 range
,避免 ALL
。
五、EXPLAIN 结果分析案例
1. 全表扫描(ALL)
EXPLAIN SELECT * FROM users WHERE age = 25;
如果 age
没有索引,type 显示为 ALL
,rows 很大,说明是全表扫描。
2. 使用索引(ref/range)
CREATE INDEX idx_age ON users(age);
EXPLAIN SELECT * FROM users WHERE age = 25;
此时 type 变为 ref
或 range
,rows 显著减少,说明走了索引。
3. 覆盖索引(Using index)
EXPLAIN SELECT age FROM users WHERE age = 25;
Extra 字段出现 Using index
,说明只用索引就能返回结果,无需回表,效率更高。
4. 联合索引与最左前缀
CREATE INDEX idx_name_age ON users(name, age);
EXPLAIN SELECT * FROM users WHERE name = 'Tom' AND age = 20;
只有 WHERE 条件从最左列开始连续使用,才能用到联合索引。
六、索引优化详解
1. 合理选择索引字段
- 经常作为查询条件(WHERE)、排序(ORDER BY)、分组(GROUP BY)、连接(JOIN)的字段应加索引。
- 选择区分度高的字段作为索引,区分度低(如性别、布尔值)不建议单独加索引。
2. 联合索引与最左前缀原则
- 联合索引(如 idx_a_b_c(a, b, c))只有从最左列开始连续使用,索引才会生效。
- 例如:WHERE a=1 AND b=2,可以用到 (a, b, c) 索引;WHERE b=2 无法用到。
3. 覆盖索引(索引覆盖查询)
- 查询的字段全部在索引中,无需回表,效率极高。
- 例如:
SELECT id, name FROM users WHERE name='Tom';
如果有联合索引 (name, id),则为覆盖索引。
4. 避免在索引列上使用函数或运算
- 如
WHERE DATE(create_time) = '2024-07-01'
,会导致索引失效。 - 优化为:
WHERE create_time >= '2024-07-01 00:00:00' AND create_time < '2024-07-02 00:00:00'
5. 合理利用唯一索引和主键
- 唯一索引和主键查询效率最高,能直接定位唯一一行。
6. 控制索引数量
- 索引不是越多越好,过多索引会影响写入性能和占用空间。
- 定期清理无用、重复的索引。
7. 利用前缀索引和全文索引
- 对于长字符串字段(如 email、url),可用前缀索引:
CREATE INDEX idx_email ON users(email(10));
- 对于大文本字段,使用 FULLTEXT 全文索引。
七、更多实战案例
案例1:避免索引失效
错误写法:
SELECT * FROM orders WHERE YEAR(order_date) = 2024;
优化写法:
SELECT * FROM orders WHERE order_date >= '2024-01-01' AND order_date < '2025-01-01';
原因: 在索引列上使用函数会导致全表扫描。
案例2:利用联合索引优化排序
原SQL:
SELECT * FROM users WHERE status = 1 ORDER BY create_time DESC;
优化:
CREATE INDEX idx_status_create_time ON users(status, create_time DESC);
效果: 查询和排序都能用到索引,避免 Using filesort。
案例3:覆盖索引提升查询效率
原SQL:
SELECT id, name FROM users WHERE status = 1;
优化:
CREATE INDEX idx_status_id_name ON users(status, id, name);
效果: 查询只用索引,无需回表,Extra 显示 Using index。
案例4:避免索引下推失效
原SQL:
SELECT * FROM users WHERE name LIKE '%abc%';
优化:
SELECT * FROM users WHERE name LIKE 'abc%';
原因: 前缀模糊匹配可用索引,%开头无法用索引。
案例5:合理利用唯一索引
原SQL:
SELECT * FROM users WHERE email = 'test@example.com';
优化:
CREATE UNIQUE INDEX idx_email ON users(email);
效果: 查询效率极高,type 为 const。
案例6:多表关联优化
原SQL:
SELECT * FROM orders o JOIN users u ON o.user_id = u.id WHERE u.status = 1;
优化:
- 给
orders.user_id
和users.id
都加索引 - 给
users.status
加索引
八、EXPLAIN FORMAT=JSON
MySQL 5.6+ 支持 EXPLAIN FORMAT=JSON
,输出更详细、结构化的信息,便于自动化分析。
EXPLAIN FORMAT=JSON SELECT * FROM users WHERE age = 25;
九、常见 Extra 字段含义
- Using where:通过 where 过滤数据
- Using index:覆盖索引,无需回表
- Using temporary:使用了临时表,常见于 group by/order by
- Using filesort:需要额外排序,性能较差
- Using join buffer:连接时用到了 join buffer,说明未用到索引
十、SQL 优化常用方法总结
- 加索引:为查询条件、排序、分组等常用字段加索引
- 优化 where 条件:避免在索引列上使用函数、计算、like '%xx%'等
- 覆盖索引:只查询索引中的字段,减少回表
- 减少返回行数:用 limit、合理的 where 条件
- **避免 select ***:只查需要的字段
- 合理设计联合索引:遵循最左前缀原则
- 避免临时表和 filesort:优化 group by、order by
- 定期用 EXPLAIN 检查 SQL 执行计划,发现并优化慢查询
十一、总结
EXPLAIN
是 SQL 优化的利器,能直观展示执行计划- 关注 type、rows、Extra 等字段,发现性能瓶颈
- 通过加索引、优化 SQL、减少全表扫描和 filesort 提升性能
- 多用
EXPLAIN
和EXPLAIN FORMAT=JSON
,结合慢查询日志,持续优化数据库
建议:
每次上线新 SQL,务必用 EXPLAIN 检查执行计划,避免潜在性能隐患!
如需更深入的 SQL 优化案例或遇到具体 SQL 性能问题,欢迎留言交流!