MySQL执行计划rows比实际多很多怎么办?手动触发采样统计

先明确一个核心判断:EXPLAIN 里那个大得吓人的 rows 值,通常不是 MySQL 在骗你,而是它的“视力”出了点问题——优化器正戴着一副度数不准的“眼镜”(过时或粗糙的统计信息)在估算数据量。这事儿,直接影响了它选择索引和执行路径的决策。
为什么 EXPLAIN 的 rows 值远大于实际扫描行数?
简单来说,这是优化器基于过时或低精度统计信息做出的估算偏差。InnoDB 引擎的默认机制是,只在表数据发生约 10% 的变动时才自动更新统计信息(由 innodb_stats_auto_recalc 参数控制)。而且,它采样分析的数据页数也相对固定(innodb_stats_persistent_sample_pages 默认是 20 页)。一旦遇到数据分布严重倾斜、表体积巨大,或者写入更新极其频繁的场景,这套默认机制给出的估算就很容易“失真”。
怎么手动触发更准的统计信息更新?
最直接有效的办法,就是使用 ANALYZE TABLE 命令。它会强制对表进行重新采样,并刷新内存和磁盘中的统计信息(如果开启了持久化统计功能)。
ANALYZE TABLE orders;
如果表特别大,担心采样时间过长,可以尝试控制采样精度:
- 临时提高采样页数:执行
SET SESSION innodb_stats_persistent_sample_pages = 100;再运行ANALYZE。采样页数越多,结果通常越准,但耗时也越长。 - 操作完成后,建议将会话变量改回默认值,以免影响后续其他查询的统计行为。
- 确认持久化统计已启用:跑一下
SHOW VARIABLES LIKE 'innodb_stats_persistent';,确保其值为ON。只有这样,ANALYZE的结果才会被保存到磁盘,避免服务器重启后失效。
rows 仍不准?这些细节容易被忽略
即便手动执行了 ANALYZE,在某些特定场景下,rows 的估算值依然可能严重偏离实际。以下几个“坑”值得特别注意:
- 查询条件使用了函数或表达式:比如
WHERE YEAR(create_time) = 2024。这种情况下,优化器往往无法有效利用索引上的统计信息,可能直接退回到非常粗略的全表估算。 - 未充分利用联合索引的最左前缀:如果索引是
(a,b,c),但查询条件是WHERE b = 1,那么关于列b的分布统计信息可能就没被有效用于这次估算。 - 查询跨多个分区:对于分区表,统计信息是按分区单独维护的。当查询涉及多个分区时,优化器可能会简单地将各分区的估算值相加,而不是进行更精确的整体探测。
- 理解
rows的真实含义:需要警惕的是,EXPLAIN输出的rows是“预估的、每次访问需要扫描的行数”,并非最终返回的结果集大小。在嵌套循环连接中,这个值会被层层乘积放大,让估算偏差显得更为夸张。
要不要关掉自动统计?
全局关闭 innodb_stats_auto_recalc 通常不是个好主意。更稳妥的运维策略是:
- 对于核心大表,可以在业务低峰期设置定时任务,定期执行
ANALYZE TABLE。 - 对于写入频繁但查询模式相对固定的表,可以开启
innodb_stats_persistent = ON,并适当调高innodb_stats_persistent_sample_pages的值(例如设为 100 到 200),以获得更稳定、更精确的统计快照。 - 如果发现某条关键查询总是因为
rows估算错误而选错索引,在深入优化之前,可以考虑使用FORCE INDEX或优化器提示(如USE INDEX)作为临时兜底方案。毕竟,统计信息永远只是近似值,不能完全依赖。
说到底,统计信息只是优化器的一个参考工具。真正关键的是,理解它在哪些条件下容易失效,并学会结合 EXPLAIN FORMAT=JSON 中提供的更详细信息(比如 filtered 字段)来交叉验证优化器的估算逻辑,这才是治本之道。
