ANALYZE TABLE是MySQL官方唯一支持的手动修复索引估算偏差的标准手段,通过重新采样更新统计信息,但受采样页数、数据倾斜等影响无法保证100%准确。

直接更新统计信息不能“避免”错误执行计划,只能降低优化器因数据分布误判而选错索引的概率。真正起作用的是让 ANALYZE TABLE 在合适时机、以合理方式运行,而不是盲目刷新。
什么时候必须手动执行 ANALYZE TABLE
自动触发靠不住,尤其在这些场景下:
- 批量导入或删除超 10% 行数后(比如 50 万行表删了 6 万),但
INFORMATION_SCHEMA.INNODB_TABLESTATS里预估行数没变——说明自动重算没触发 - 新建表并快速写入大量数据,首次
EXPLAIN就发现rows为 0 或极小值,STATS_INITIALIZED字段是NULL或Pending - 业务上线新功能后,某字段值分布突变(如
status从 95% 'pending' 变成 80% 'done'),旧统计严重失真 - 使用了前缀索引(如
INDEX (name(10))),但查询条件匹配完整值,cardinality估算完全失效
ANALYZE TABLE 执行时到底做了什么
它不是全表扫描,而是采样估算:InnoDB 默认读取 innodb_stats_sample_pages(默认 20)个随机数据页,每页抽样部分记录,计算列值唯一性(cardinality)和分布直方图。这意味着:
- 结果天然有误差,尤其当数据倾斜严重(如时间戳集中在最近 2 小时,但采样页落在冷区)
- 对大表,耗时主要来自遍历 B+ 树叶子页链表找那 20 页,不是 I/O 带宽瓶颈
- 会加 MDL 共享锁:不阻塞
SELECT/INSERT,但会阻塞其他ANALYZE和所有ALTER TABLE - MyISAM 表行为完全不同——它是精确计算,且加表级读锁,期间写入会被卡住
如何安全、有效地触发统计更新
别用脚本暴力扫全库,重点盯住核心大表和近期变更频繁的表:
- 先确认表是否支持自动重算:
SELECT STATS_AUTO_RECALC FROM INFORMATION_SCHEMA.INNODB_TABLES WHERE NAME = 'db/table_name';,返回1才生效;否则需ALTER TABLE t STATS_AUTO_RECALC=1; - 对单表强制刷新:
ANALYZE TABLE orders;,线上建议避开高峰,加超时:timeout 300 mysql -uadmin -e "ANALYZE TABLE orders;" - 避免在从库执行——虽然语句不复制,但可能干扰 SQL 线程(尤其 GTID 模式下)
- 小表(innodb_stats_auto_recalc 压根不会触发,必须手动;可写轻量检查逻辑:
SELECT table_name, table_rows FROM INFORMATION_SCHEMA.TABLES WHERE table_schema='db' AND table_rows > 10000;
最常被忽略的一点:统计信息不准本身不是问题,问题是优化器基于它做的代价估算偏离真实成本。哪怕你调高采样页数,如果数据分布本身高度倾斜,cardinality 依然会错——这时候该考虑调整索引设计或加 FORCE INDEX,而不是反复 ANALYZE。


















