EXPLAIN ANALYZE 会真实执行 SQL,仅支持 MySQL ≥ 8.0.18 的 SELECT,不适用于 UPDATE/DELETE;其 actual time 反映真实耗时,需结合 loops 计算总开销,三信号(actual time 差值大、rows 显著偏离预估、loops 异常高)可快速定位性能瓶颈。

EXPLAIN ANALYZE 会真实执行 SQL,别在生产环境直接跑
它不是“看计划”的 EXPLAIN,而是真把语句跑一遍,记录每个算子的耗时、行数和调用次数。这意味着:
— 如果原查询要 3 秒,EXPLAIN ANALYZE 至少也要 3 秒
— 它会加锁、写临时表、触发触发器、消耗 buffer pool,可能阻塞其他事务
— 对 UPDATE/DELETE 直接报错:ERROR 1288: The target table of the UPDATE is not updatable in EXPLAIN ANALYZE
— 仅支持 SELECT,且要求 MySQL ≥ 8.0.18(8.0.12 起有雏形但不完整)
怎么看 actual time 字段里的耗时数据
actual time 格式是 start..end(单位毫秒),差值才是该算子自身耗时;但要注意嵌套关系:
— 最内层扫描(如 Index lookup on t2 using idx_b)的 end - start 是它单次真实耗时
— 上层 Nested loop inner join 的 end 包含所有子节点总耗时,不是“额外开销”
— 若出现 loops=500,则该算子被调用了 500 次,总耗时 ≈ (end - start) × loops,不是单次值
— 示例:Index lookup on orders using idx_user_id (actual time=0.010..0.010 rows=1 loops=500) 表示每次查 0.01ms,但总共执行了 500 次,光这一步就占了约 5ms
三个信号快速定位瓶颈
不用通读整个输出,盯住这三项就能判断问题在哪:
— actual time 区间过大:比如 actual time=120.456..2890.123(差值超 2.7 秒),说明这个算子本身慢,优先检查是否缺失索引或统计信息过期
— rows 远大于 filtered × 预估 rows:例如预估 rows=100、filtered=10.00%,但实际 rows=850,说明优化器严重误判,ANALYZE TABLE users 可能立刻见效
— loops 明显异常:比如驱动表返回 500 行,导致关联子查询或索引查找被调用 500 次,这是 JOIN 顺序或条件写法问题,不是加索引能解决的
FORMAT=TREE 是默认输出格式,别手动指定
EXPLAIN ANALYZE 内置使用树形结构,天然反映算子嵌套层级;而 EXPLAIN FORMAT=TRADITIONAL 是扁平表格,看不出谁依赖谁。
— 不要写成 EXPLAIN ANALYZE FORMAT=TRADITIONAL ...,MySQL 会忽略 FORMAT 并警告
— 也不建议用 EXPLAIN FORMAT=JSON 替代,它的 cost_info 是优化器估算成本,单位非毫秒,无法换算成真实时间
— 真实耗时只存在于 EXPLAIN ANALYZE 的树形输出中,且必须带 actual time 字段才有效
最容易被忽略的是 loops 和 actual time 的乘积关系——很多人只看单次耗时,却没意识到“查得快但查得勤”才是慢的根源。还有就是误把 EXPLAIN 当成可执行分析工具,反复在生产库上试错。


















