登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  MySQL

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 看,不能只盯着总行数。

MySQL EXPLAIN 估算路径中查询条件、统计信息、访问路径、rows 和实际行数的关系说明图
图1:MySQL EXPLAIN 估算路径说明图,展示查询条件经过统计信息形成 rows 估算后,与实际执行结果分属两个观察域;这是静态结构图,不是运行截图。

用 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,确认计划与实际耗时是否真的改善。

MySQL ANALYZE TABLE、索引基数、直方图和 COLUMN_STATISTICS 之间关系的说明图
图2:统计信息修正关系图,说明 ANALYZE TABLE、索引分布、列直方图和 filtered 估算之间的静态关系;这是结构说明图,不是数据库控制台截图。

差距出现后用这张清单收敛判断

观察现象优先检查处理边界
单表 rows 远小于 actual rows统计更新时间、索引基数、数据倾斜先 ANALYZE TABLE,再复测
rows 接近但耗时很高回表、磁盘 I/O、循环次数、返回列不要只改统计信息
单表正常,连接后行数膨胀连接条件、列相关性、连接顺序检查重复匹配与过滤位置
更新统计后计划变差直方图是否过期、索引是否适合场景保留对照计划,必要时回退变更

只有当实际耗时、I/O 或并发影响确实异常时,才进入索引设计或提示评估。FORCE INDEX 可以作为受控对照实验,但不要把它当成修复统计误差的默认答案。

常见问题

rows 越大就一定越慢吗?

不一定。访问类型、缓存命中、回表次数、返回数据量和连接循环都会影响耗时。rows 是重要线索,但必须和 actual rows、loops、actual time 一起看。

EXPLAIN ANALYZE 会不会改变数据?

它会执行被分析的语句。对 SELECT 通常关注读取负载;对 UPDATE、DELETE 等语句必须先在隔离环境确认影响,不能把它当成只读计划查看器。

什么时候该加索引而不是更新统计?

当统计已更新、实际过滤仍扫描大量无用行,并且查询模式稳定时,再根据谓词顺序、排序和回表需求设计索引;不要用一次异常样本替代长期查询画像。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>