EXPLAIN不显示各阶段耗时,仅提供预估执行计划;真实耗时需用EXPLAIN ANALYZE(MySQL 8.0.12+)查看actual_time字段,或结合performance_schema/events_statements_history与stages统计。

EXPLAIN 不显示各阶段耗时,别白费劲查 execution_time 字段
EXPLAIN(包括 EXPLAIN FORMAT=TRADITIONAL 和 EXPLAIN FORMAT=JSON)本身不提供任何时间维度信息。MySQL 8.0.12+ 的 EXPLAIN ANALYZE 才真正执行语句并统计各节点实际耗时,但很多人误以为普通 EXPLAIN 也能看到“Sending data”“Copying to tmp table”这些状态的毫秒数——它只显示状态,不显示时间。
常见错误现象:EXPLAIN 输出里看到 Extra 列含 Using filesort 或 Using temporary,就断定“这里慢”,却没验证是否真发生、持续多久;或者在慢查询日志里看到 Query_time: 1.234567,却想用 EXPLAIN 拆解这 1.2 秒分摊在哪一步——做不到。
-
EXPLAIN是预估执行计划,不执行语句,自然无耗时数据 -
EXPLAIN FORMAT=JSON中的cost_info是优化器估算成本,单位非毫秒,不可直接换算成时间 - 想看真实阶段耗时,必须用
EXPLAIN ANALYZE(MySQL 8.0.12+)或搭配 performance_schema
EXPLAIN ANALYZE 能看到哪些阶段耗时?怎么看嵌套循环的开销
EXPLAIN ANALYZE 会真实执行语句(注意:有副作用!勿在生产环境对写操作或大结果集直接用),并在 JSON 格式输出中为每个计划节点附带 actual_time 字段,包含 start 和 end(单位微秒),以及 rows_examined、rows_produced 等实测指标。
关键点在于:耗时是**自底向上累加**的。最内层表扫描的 actual_time.end 就是它自身耗时;上层 Nested Loop Join 的 actual_time.end 包含了所有子节点总耗时。
-> Nested loop inner join (cost=1.40 rows=1) (actual time=0.123..12.456 rows=1 loops=1)
-> Index lookup on t1 using idx_a (a=1) (cost=0.35 rows=1) (actual time=0.045..0.048 rows=1 loops=1)
-> Filter: (t2.b > 100) (cost=1.05 rows=1) (actual time=0.078..12.402 rows=1 loops=1)
-> Index lookup on t2 using idx_b (b>100) (cost=0.35 rows=10) (actual time=0.062..12.320 rows=10 loops=1)
上面例子中,t2 的 actual time=0.062..12.320 表示该扫描实际花了约 12.26ms;而外层 Join 的 0.123..12.456 基本等于它 + t1 的耗时之和。注意 loops=1 表示该节点只执行一次;若为 loops=10,则 actual_time 是单次均值,总耗时需乘以 loops。
为什么 EXPLAIN ANALYZE 显示的耗时和 slow_log 不一致?
差异主要来自三类开销未被 EXPLAIN ANALYZE 覆盖:
- 网络传输耗时(发送结果集给客户端)——
EXPLAIN ANALYZE在服务端完成即停,不计网络 - 锁等待(如
Waiting for table metadata lock)——它只统计执行阶段,不含排队时间 - 解析、权限检查、结果集序列化等 server 层开销——
EXPLAIN ANALYZE只聚焦存储引擎层及优化器路径
典型表现:慢查询日志里 Query_time: 3.214,但 EXPLAIN ANALYZE 总 actual_time.end 加起来才 800ms。此时应查 performance_schema.events_statements_history_long 或开启 long_query_time=0 配合 log_output=TABLE 定位非执行阶段瓶颈。
没有 EXPLAIN ANALYZE 怎么近似定位阶段耗时?
MySQL 5.7 或低版本只能靠组合手段逼近:
- 启用
performance_schema并打开相关 instruments:UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE 'statement/sql/%' OR NAME LIKE 'stage/%'; - 执行语句后查
performance_schema.events_statements_history_long,找对应SQL_TEXT,再关联events_stages_history_long查各 stage(如stage/sql/Sorting result、stage/sql/Creating sort index)的TIMER_WAIT(皮秒单位,除以 10⁹ 得秒) - 用
SHOW PROFILE FOR QUERY N(已弃用但部分实例仍可用),它能列出status如Creating tmp table对应的耗时,但精度低且不支持并发会话
注意:performance_schema 开销不小,不宜长期全量开启;stage 名称随 MySQL 版本变化(如 8.0.16+ 把 Copying to tmp table 改为 Creating tmp table),查之前先确认 setup_actors 和 setup_consumers 已正确配置。
真正耗时的阶段往往藏在 stage 统计里,而不是 EXPLAIN 的 Extra 描述中——比如 Using filesort 看似可怕,但若 stage/sql/Sorting result 耗时仅 0.2ms,其实无关痛痒;反过来,stage/sql/Sending data 占了 90% 时间,说明是大结果集或索引覆盖不足,这才是重点。


















