MySQL EXPLAIN 的 rows 和实际扫描量差距大怎么办
来源:17golang原创
时间:2026-10-23 04:07:51 419浏览 收藏
平时用EXPLAIN分析慢SQL的时候,经常能碰到预估出来的rows行数,和实际执行扫描的行数差出好几倍甚至几十上百倍,这种情况本质上是MySQL优化器用来做决策的统计信息出现了偏差,顺着统计信息采样、条件过滤计算、表关联逻辑这几个维度排查就能定位到具体原因。
如果差距是统计信息采样误差导致的,重新执行ANALYZE TABLE刷新对应表的统计数据大多能直接解决问题,如果是索引选择度低、过滤条件计算不准带来的误判,就可以通过调整优化器开关、改写SQL、补充更合适的索引来修正。
遇到 EXPLAIN 里的 rows 和实际扫描量差距很大时,先不要急着改索引。rows 是优化器根据统计信息推算出的待检查行数,不是执行计数;真正需要对照的是同一条 SQL 的估算行数、实际行数、循环次数和耗时。
官方地址:https://dev.mysql.com/doc/refman/8.4/en/explain.html
rows与filtered都是计划阶段的估算,不能直接当作线上扫描计数。EXPLAIN ANALYZE会执行查询,用 TREE 输出估算与实际结果,适合定位偏差位置。- 优先更新统计信息和检查数据分布,再决定是否调整索引;只有 rows 偏差不等于计划一定错误。
先区分 rows、filtered 和实际扫描量
传统表格格式中,rows 表示 MySQL 认为执行该访问路径需要检查的行数。对 InnoDB 来说它是估算值,可能受索引基数、采样和数据分布影响。filtered 表示表条件预计能保留下来的百分比,不能理解成已经统计出的真实过滤比例。
例如某一步显示 rows=10000、filtered=1.00,计划阶段推断大约有 100 行会进入后续连接;这和存储引擎实际读了多少页、发生了多少次回表不是同一个指标。连接查询还要结合每个迭代器的 loops 看,不能只盯着总行数。

用 EXPLAIN ANALYZE 把估算和实际放在一起
排查时固定查询文本、绑定参数和数据环境,先看计划,再在可接受的环境中使用 EXPLAIN ANALYZE。它会真正执行语句,输出每个迭代器的预计行数、实际行数、首行时间、执行时间和循环次数;因此包含写入的语句要特别谨慎,读查询也要考虑负载。
-- 先看优化器计划,不执行 SELECT 本身
EXPLAIN FORMAT=TREE
SELECT order_id, created_at
FROM orders
WHERE customer_id = 42 AND status = 'paid';
-- 在可控环境中实际执行,用于对照 rows、actual rows 和 loops
EXPLAIN ANALYZE FORMAT=TREE
SELECT order_id, created_at
FROM orders
WHERE customer_id = 42 AND status = 'paid';
重点不是把估算值强行调到完全相等,而是找出偏差最大的节点:如果表扫描的预计与实际差距大,优先看统计信息;如果单表估算还可以、连接后的行数突然膨胀,则要看相关列、连接顺序和过滤条件是否被错误独立估计。
先更新统计信息,再判断是否需要改索引
大量写入、批量删除或数据分布明显变化后,旧统计信息会让优化器做出不准确的选择。可以先检查索引基数和统计更新时间,再在业务低峰执行:
-- 更新表和索引的分布统计;生产环境先评估锁与资源开销
ANALYZE TABLE orders;
-- 查看优化器可见的列直方图摘要
SELECT TABLE_NAME, COLUMN_NAME,
HISTOGRAM->>'$."sampling-rate"' AS sampling_rate
FROM INFORMATION_SCHEMA.COLUMN_STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'orders';
MySQL 8.4 的 InnoDB 持久化统计默认用于保持计划稳定,但它仍然是估计;大变化后不能只等重启来“刷新”。对于没有合适索引、过滤值分布又很偏的列,可以有针对性地建立直方图:
-- 只为倾斜明显的过滤列建立直方图,桶数按数据分布调整
ANALYZE TABLE orders
UPDATE HISTOGRAM ON status WITH 32 BUCKETS;
直方图主要帮助非索引列的选择性估算,且会随数据变化逐渐过期;索引列的范围估算、索引探针等路径可能优先于直方图。更新后重新执行同一条 EXPLAIN 和 EXPLAIN ANALYZE,确认计划与实际耗时是否真的改善。

差距出现后用这张清单收敛判断
| 观察现象 | 优先检查 | 处理边界 |
|---|---|---|
| 单表 rows 远小于 actual rows | 统计更新时间、索引基数、数据倾斜 | 先 ANALYZE TABLE,再复测 |
| rows 接近但耗时很高 | 回表、磁盘 I/O、循环次数、返回列 | 不要只改统计信息 |
| 单表正常,连接后行数膨胀 | 连接条件、列相关性、连接顺序 | 检查重复匹配与过滤位置 |
| 更新统计后计划变差 | 直方图是否过期、索引是否适合场景 | 保留对照计划,必要时回退变更 |
只有当实际耗时、I/O 或并发影响确实异常时,才进入索引设计或提示评估。FORCE INDEX 可以作为受控对照实验,但不要把它当成修复统计误差的默认答案。
常见问题
rows 越大就一定越慢吗?
不一定。访问类型、缓存命中、回表次数、返回数据量和连接循环都会影响耗时。rows 是重要线索,但必须和 actual rows、loops、actual time 一起看。
EXPLAIN ANALYZE 会不会改变数据?
它会执行被分析的语句。对 SELECT 通常关注读取负载;对 UPDATE、DELETE 等语句必须先在隔离环境确认影响,不能把它当成只读计划查看器。
什么时候该加索引而不是更新统计?
当统计已更新、实际过滤仍扫描大量无用行,并且查询模式稳定时,再根据谓词顺序、排序和回表需求设计索引;不要用一次异常样本替代长期查询画像。
-
374 收藏
-
499 收藏
-
384 收藏
-
184 收藏
-
265 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习