bigywater.tech
MySQL / InnoDB · 31

优化器统计信息为什么会过期

插入大量新数据不手动ANALYZE,验证InnoDB的统计信息是否自动更新,再发现真正的估算误差根源其实是抽样,不是过期。

auto_recalc=ON
证明统计信息不是完全被动的
20个采样页
误差的根源,不是过期
约40%偏差
手动ANALYZE后依然存在的误差

做的事:插入大量新数据,不主动ANALYZE,看优化器会不会自己更新

很多人以为"没跑ANALYZE TABLE,统计信息就是老的"——这次验证InnoDB是不是真的这么被动。

操作建表插入10万行status='active' EXPLAIN SELECT * FROM t13 WHERE status='active' → 估算 rows=49999 又插入40万行status='banned',中间没有执行任何ANALYZE EXPLAIN SELECT * FROM t13 WHERE status='banned' → 估算 rows=249999

验证:没手动ANALYZE,估算数字也跟着变了

如果统计信息真的是"过期不更新",插入40万行banned之后估算不该有任何反应。

结果SHOW VARIABLES LIKE 'innodb_stats_auto_recalc' → ON 插入40万行banned后,没手动ANALYZE,估算变成了249999 (的banned行数是400000)

原因:innodb_stats_auto_recalc 默认是开着的——InnoDB会在一张表的数据变动累积到一定比例后,自动在后台触发一次统计信息的重新采样,不需要人为手动执行ANALYZE TABLE。插入40万行banned之后,估算数字确实变了(从0变成249999),说明自动重算生效了。

验证:真正的问题不是"过期",是"抽样"

哪怕自动重算了,估算的249999跟的400000还是差了近40%。

结果SHOW VARIABLES LIKE 'innodb_stats_persistent_sample_pages' → 20 执行ANALYZE TABLE t13之后: EXPLAIN SELECT * FROM t13 WHERE status='banned' → 估算 rows=249747(跟没执行ANALYZE前的249999几乎没差) 的banned行数: 400000(COUNT(*)验证)
的关键发现:手动执行 ANALYZE TABLE 之后,估算数字几乎没变(249999→249747),跟的400000相比,误差还是接近40%。真正的原因不是"统计信息过期没更新",而是InnoDB的持久化统计信息本来就是抽样出来的——默认只采样 innodb_stats_persistent_sample_pages=20 个数据页,用这20个页面的抽样比例去推算整张表的分布。数据分布越倾斜(这张表95%对5%的极端比例),抽样误差就越容易被放大。想让估算更准,能做的是调大采样页数(但会让ANALYZE本身更慢),或者用下一篇会讲的直方图来辅助——单纯"记得跑ANALYZE TABLE"解决不了这个问题,因为它本来就不是"过没过期"的问题。

分步演示:统计信息是怎么"过期"的

每一步都是本文上面用EXPLAIN跑出来的原始估算结果。

统计信息自动在更新,误差的根源是抽样不是过期
这一步新验证的状态

    本文用到的工具

    • 本机 MySQL 26.7.0(InnoDB),innodb_stats_auto_recalc=ON
    • ANALYZE TABLE / innodb_stats_persistent_sample_pages:验证统计信息的更新与抽样误差
    京ICP备2026067502号-1京公网安备11010502063487号