深入理解 MySQL 的 EXPLAIN ANALYZE
在 SQL 优化中,执行计划是分析查询性能的核心依据。MySQL 的
EXPLAIN
命令能提供查询的预估执行路径,但仅凭预估数据有时难以精准定位问题 —— 毕竟统计信息可能存在偏差,实际执行与预期往往存在差异。而EXPLAIN ANALYZE
的出现,填补了这一空白:它不仅展示执行计划,还会实际执行 SQL 并返回真实的执行数据,为优化提供更可靠的依据。本文将通过实例解析EXPLAIN ANALYZE
的用法与价值。一、EXPLAIN 与 EXPLAIN ANALYZE 的核心差异
EXPLAIN
仅基于表和索引的统计信息生成预估执行计划,包含预估成本、扫描行数等;而EXPLAIN ANALYZE
会真实执行查询,在预估信息外增加实际执行时间、真实扫描行数、循环次数等关键数据。特性 | EXPLAIN | EXPLAIN ANALYZE |
---|---|---|
是否执行 SQL | 否(仅生成预估计划) | 是(实际执行并返回结果) |
核心数据 | 预估成本、预估行数 | 预估数据 + 实际时间、真实行数、循环次数 |
适用场景 | 初步分析执行路径 | 精准定位预估与实际的偏差 |
二、EXPLAIN ANALYZE 的输出解析
EXPLAIN ANALYZE
的输出在EXPLAIN
基础上增加了(actual ...)
部分,包含三个核心指标:actual time=xxx..xxx
:实际执行时间(毫秒),前值为单步起始时间,后值为总耗时;rows=xxx
:实际扫描的行数;loops=xxx
:该步骤的循环执行次数。
这些数据能直接反映查询的真实性能瓶颈,例如 “预估扫描 100 行,实际扫描 10000 行” 可能意味着索引失效,“实际时间过长” 可能提示连接方式或过滤条件不合理。
三、实战案例:从三个场景看 EXPLAIN ANALYZE 的价值
1. 全表查询:暴露预估与实际的行数偏差
场景:查询表
y1
的所有数据,对比EXPLAIN
与EXPLAIN ANALYZE
的差异。-
EXPLAIN
输出(预估):explain format=tree select * from y1\G *************************** 1. row *************************** EXPLAIN: -> Table scan on y1 (cost=100553 rows=1e+6)
仅显示预估成本(100553)和预估扫描行数(100 万行)。 -
EXPLAIN ANALYZE
输出(真实):explain analyze select * from y1\G *************************** 1. row *************************** EXPLAIN: -> Table scan on y1 (cost=100553 rows=1e+6) (actual time=0.0339..145 rows=1e+6 loops=1)
新增真实数据:单步执行耗时 0.0339 毫秒到 145 毫秒,实际扫描行数 100 万行,循环 1 次。
价值:若实际行数与预估偏差过大(如预估 10 万实际 100 万),可能说明表统计信息过时,需执行ANALYZE TABLE
更新。
2. 表连接查询:定位连接效率问题
场景:内连接表
t1
和t2
(通过id
关联),计算匹配的总记录数,分析连接效率。-
EXPLAIN ANALYZE
输出:explain analyze select count(*) from t1 a join t2 b using(id)\G *************************** 1. row *************************** EXPLAIN: -> Aggregate: count(0) (cost=6856.58 rows=10169) (actual time=28.230..28.230 rows=1 loops=1) -> Nested loop inner join (cost=5839.68 rows=10169) (actual time=0.057..27.721 rows=10000 loops=1) -> Index scan on b using idx_log_date (cost=1049.90 rows=10169) (actual time=0.044..2.579 rows=10000 loops=1) -> Single-row index lookup on a using PRIMARY (id=b.id) (cost=0.37 rows=1) (actual time=0.002..0.002 rows=1 loops=10000)
解析:- 最上层聚合(
count(*)
):预估 10169 行,实际 1 行,耗时 28 毫秒; - 嵌套循环连接:预估 10169 行,实际 10000 行,耗时 27 毫秒;
- 表
b
通过索引idx_log_date
扫描:实际 10000 行,耗时 2.5 毫秒; - 表
a
通过主键查找:每次循环扫描 1 行,共 10000 次循环,单步耗时 0.002 毫秒。
若增加过滤条件a.r1=10
,输出中会新增Filter
步骤,显示过滤的实际耗时和行数,帮助判断过滤条件是否有效。 - 最上层聚合(
3. 派生表查询:揭示子查询的真实开销
场景:对派生表(子查询结果)进行过滤,分析子查询与主查询的执行效率。
-
EXPLAIN ANALYZE
输出:explain analyze select * from (select * from t1 where 1 order by r1 desc limit 1000) T where r2 < 1000\G *************************** 1. row *************************** EXPLAIN: -> Filter: (T.r2 < 1000) (cost=102.91..115.00 rows=333) (actual time=5.524..5.695 rows=1000 loops=1) -> Table scan on T (cost=0.01..15.00 rows=1000) (actual time=0.002..0.041 rows=1000 loops=1) -> Materialize (cost=102.89..117.87 rows=1000) (actual time=5.521..5.623 rows=1000 loops=1) -> Limit: 1000 row(s) (cost=2.87 rows=1000) (actual time=0.552..5.197 rows=1000 loops=1) -> Index scan on t1 using idx_r1 (reverse) (cost=2.87 rows=1000) (actual time=0.551..5.132 rows=1000 loops=1)
解析:- 子查询先通过索引
idx_r1
反向扫描,取前 1000 行(LIMIT
),耗时 5.13 毫秒; - 子查询结果被物化(
Materialize
)为临时表T
,耗时 5.62 毫秒; - 主查询过滤
T.r2 < 1000
,实际扫描 1000 行,耗时 5.69 毫秒。
这里预估过滤行数 333 与实际 1000 行偏差较大,提示统计信息可能不准确,需进一步优化子查询或索引。 - 子查询先通过索引
四、使用 EXPLAIN ANALYZE 的注意事项
- 实际执行的影响:
EXPLAIN ANALYZE
会真实执行 SQL,对于UPDATE
、DELETE
等写操作,需先备份数据或在测试环境使用,避免影响生产数据。 - 耗时查询的风险:对于全表扫描、复杂连接等耗时查询,
EXPLAIN ANALYZE
可能长时间占用资源,建议先通过EXPLAIN
初步分析,必要时再用EXPLAIN ANALYZE
验证。 - 版本支持:
EXPLAIN ANALYZE
在 MySQL 8.0 及以上版本支持,低版本需升级后使用。
五、总结
EXPLAIN ANALYZE
通过 “实际执行 + 真实数据”,弥补了EXPLAIN
仅靠预估的局限性,尤其适合解决以下问题:- 预估行数与实际行数偏差过大导致的优化失效;
- 连接方式(如嵌套循环、哈希连接)的实际效率差异;
- 子查询、派生表的隐藏开销。
在 SQL 优化中,先用
EXPLAIN
定位大致路径,再用EXPLAIN ANALYZE
验证真实执行情况,两者结合能让优化更精准、高效。掌握这一工具,将大幅提升数据库性能调优的效率