MySQL EXPLAIN ANALYZE对比估算行数与实际耗时的实现方法
来源:17golang原创
时间:2026-09-15 23:51:12 262浏览 收藏
排查 MySQL 慢查询时,EXPLAIN 只能告诉你优化器“预计怎么执行”,而 EXPLAIN ANALYZE 会真正运行语句,并把每个迭代器的估算行数、实际行数、首行时间、执行耗时和循环次数放在同一棵 TREE 计划里。最实用的做法不是盯着总耗时,而是从最底层扫描节点开始,找出估算与实际偏差最大的地方,再决定是否更新统计信息、调整索引或改变 SQL。
官方资料:https://dev.mysql.com/doc/refman/8.4/en/explain.html
- 先用普通 EXPLAIN 留下不执行查询的计划基线,再用 ANALYZE 获取真实数据。
- 实际行数远大于估算行数,通常意味着选择性或统计信息判断失准,但不能单凭倍率下结论。
- actual time 是毫秒,包含子迭代器的时间;多次 loops 时,显示的是平均每次循环耗时。
先用 EXPLAIN 看优化器的假设
我会先保存原 SQL,然后执行普通的 EXPLAIN。它不会真正取数,适合先看表连接顺序、访问类型、候选索引、使用索引以及估算的 rows。这一步的价值是留下“优化器原本打算怎么做”的基线,不要一上来就改索引。
-- 先保存不执行查询的计划,便于和实测树逐节点比较
EXPLAIN
SELECT o.customer_id, SUM(o.amount) AS total_amount
FROM orders AS o
WHERE o.status = 'paid'
AND o.created_at >= '2026-01-01'
GROUP BY o.customer_id;
重点记录过滤条件所在表的访问类型、估算行数和是否出现全表扫描。普通 EXPLAIN 反映的是估算,不代表这次请求一定会按这个成本和耗时完成。
用 EXPLAIN ANALYZE 把估算换成实际观测
确认语句可以在当前环境执行后,再运行:
-- ANALYZE 会执行 SELECT;生产环境先控制范围并确认副作用
EXPLAIN ANALYZE FORMAT=TREE
SELECT o.customer_id, SUM(o.amount) AS total_amount
FROM orders AS o
WHERE o.status = 'paid'
AND o.created_at >= '2026-01-01'
GROUP BY o.customer_id;
MySQL 8.4 的 EXPLAIN ANALYZE 固定使用 TREE 格式。典型节点会同时出现 cost=... rows=... 和 (actual time=首行..末行 rows=... loops=...)。其中 actual time 单位是毫秒;当一个节点被循环执行多次时,时间字段是平均每次循环的值,不应直接乘 loops 后再和整条 SQL 的总耗时重复相加。

沿着叶子节点比较倍率和耗时
比较时按“扫描或索引节点 → 过滤节点 → 聚合或连接节点”的顺序向上看。可以用下面的检查表避免把不同指标混在一起:
| 观察项 | 它回答什么 | 偏差时先检查 |
|---|---|---|
| 估算 rows / actual rows | 优化器对选择性是否判断准确 | 统计信息、数据分布、条件相关性 |
| actual time | 哪个迭代器消耗了执行时间 | 访问路径、回表、排序或聚合成本 |
| loops | 节点被父节点调用了多少次 | 连接顺序、嵌套循环和外层行数 |
例如某个索引范围扫描估算 20 行,实际返回 20000 行,偏差达到三个数量级,优先怀疑过滤列的分布或统计信息,而不是立即添加一个更宽的索引。相反,如果行数接近但单次 actual time 很高,应继续看是否发生大量回表、函数计算或磁盘读取。父节点时间包含子节点,定位时要避免把父子耗时简单相加。

根据偏差选择最小修复动作
如果偏差集中在基表过滤节点,先在数据变化明显后更新统计信息,再复跑同一条语句:
-- 更新优化器可用的表统计信息,再观察计划是否改变
ANALYZE TABLE orders;
-- 用同一条语句复测,避免把数据变化误判成索引收益
EXPLAIN ANALYZE FORMAT=TREE
SELECT o.customer_id, SUM(o.amount) AS total_amount
FROM orders AS o
WHERE o.status = 'paid'
AND o.created_at >= '2026-01-01'
GROUP BY o.customer_id;
如果统计信息更新后估算仍明显失准,再检查过滤列组合、索引列顺序和连接条件;如果行数估算正常但耗时集中在某个节点,则应针对访问路径或计算阶段做最小改动。每次只改一个因素,并保存修改前后的 TREE 输出。
常见问题
EXPLAIN ANALYZE 会不会修改数据?
对 SELECT 诊断本身不会修改业务表,但它会真正执行语句并消耗 CPU、IO 和锁资源。涉及 UPDATE、DELETE 或复杂查询时,必须先确认环境、权限和副作用。
为什么不能把估算 rows 当成真实返回行数?
估算 rows 是优化器基于统计信息和条件选择性推导的计划输入,只有 actual rows 才代表这次执行观测到的迭代器输出。
actual time 越大就一定要加索引吗?
不一定。先看行数偏差、loops 和节点类型;排序、聚合、回表或连接顺序都可能是耗时来源,加索引只是其中一种方案。
最终复查时,至少保留普通 EXPLAIN、EXPLAIN ANALYZE 和修复后的 EXPLAIN ANALYZE 三份结果。这样才能确认变化来自统计信息、索引或 SQL 结构,而不是一次偶然的缓存状态。
-
374 收藏
-
499 收藏
-
384 收藏
-
184 收藏
-
265 收藏
-
475 收藏
-
202 收藏
-
数据库 · MySQL | 5小时前 | MySQL · SQL查询 · 窗口函数 · ROW_NUMBER · 数据分组 · ROW_NUMBER PARTITION BY MySQL 窗口函数 每组最新记录 分组取最新185 收藏
-
148 收藏
-
409 收藏
-
118 收藏
-
101 收藏
-
221 收藏
-
数据库 · MySQL | 13小时前 | MySQL · 性能分析 · Performance Schema · SQL耗时 · mysql Performance Schema events_statements 平均耗时125 收藏
-
167 收藏
-
253 收藏
-
312 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习