analyze table 是为更新统计信息以确保优化器生成准确执行计划,防止因统计过时导致 explain 的 rows、type、key 等失真;其效果受 innodb_stats_persistent 设置影响,需结合数据分布与业务负载合理执行。

定期执行 ANALYZE TABLE 不是为了“刷存在感”,而是防止优化器在生成执行计划时“靠猜”——一旦统计信息过时,EXPLAIN 显示的 rows、key、type 就可能严重失真,导致本该走索引的查询变成全表扫描。
为什么 ANALYZE TABLE 会直接影响 EXPLAIN 的输出结果
MySQL 优化器依赖统计信息估算成本:比如某列的值分布、索引的选择性、表的行数等。这些数据不是实时计算的,而是采样后缓存在内存(或磁盘,取决于配置)中。当表发生大量变更(如批量 INSERT、UPDATE、DELETE),旧统计信息仍被沿用,EXPLAIN 就会基于错误前提做判断。
典型表现:
-
rows列显示几千,实际要扫百万行(低估导致选错索引) -
type是ALL而不是预期的ref或range -
key显示NULL,但明明有可用索引 - 执行计划在测试环境 OK,上线后变慢——因为生产数据量/分布已变,但统计信息没更新
什么情况下必须手动 ANALYZE TABLE
自动更新不总是可靠,尤其在以下场景中,必须人工干预:
- 执行完大批量 DML 后:例如
INSERT INTO orders SELECT ... FROM staging导入 50 万行 - 清空并重建大表:如
TRUNCATE TABLE logs后再灌入新数据 - 升级或迁移后首次启动:若
innodb_stats_persistent=OFF,重启后统计信息丢失,首次查询会触发延迟收集(卡顿明显) - 发现某张表的
EXPLAIN结果与实际执行耗时严重不符,且排除了锁、IO、缓存干扰
注意:ANALYZE TABLE 是轻量操作,不锁表(InnoDB 下为元数据锁 + 短暂读锁),但对超大表(>1TB)仍建议在低峰期执行。
innodb_stats_persistent 开关对 ANALYZE 行为的影响
这个参数决定统计信息是否落盘。它的取值直接改变 ANALYZE TABLE 的效果持久性:
- 若
innodb_stats_persistent=ON(默认):ANALYZE TABLE写入mysql.innodb_table_stats和mysql.innodb_index_stats,重启不失效 - 若
innodb_stats_persistent=OFF:ANALYZE TABLE只更新内存副本,MySQL 重启后立即失效,下次访问该表时才会重新采样(可能引发首次查询抖动) - 采样页数也关键:
innodb_stats_persistent_sample_pages默认 20,对倾斜数据(如 99% 值为 'active')可能不够,需酌情调高
检查当前设置:SELECT @@innodb_stats_persistent, @@innodb_stats_persistent_sample_pages;
如何安全地对整个库执行 ANALYZE
单表 ANALYZE TABLE orders; 很简单,但整库批量操作容易误伤系统负载。推荐方式:
- 用脚本生成语句,避免拼接错误:
SET @db = 'myapp'; SELECT CONCAT('ANALYZE TABLE ', table_name, ';') FROM information_schema.tables WHERE table_schema = @db AND table_rows > 1000; - 逐个执行,加
SLEEP(0.1)防止并发冲击(尤其在低配实例上) - 跳过临时表、系统表、日志表等非业务表:
AND table_type = 'BASE TABLE' - 生产环境务必加超时控制和错误捕获,例如用存储过程封装,并记录失败表名
别用 SELECT GROUP_CONCAT(...) 一次性拼太长的 SQL——超过 max_allowed_packet 会截断,且无法定位哪张表出错。
最常被忽略的一点:统计信息不是越新越好,而是要匹配真实负载模式。比如凌晨批量导入后立刻 ANALYZE,但白天查询集中在某几个范围,这时直方图(histogram)比默认采样更有用——不过 MySQL 8.0+ 才原生支持,老版本只能靠调整 sample_pages 或定期 ANALYZE 折中应对。











