ANALYZE TABLE 主要用于更新索引基数(Cardinality)等统计信息以优化查询执行计划,但受 innodb_stats_persistent 关闭、采样页数不足、数据倾斜或长事务阻塞等因素影响,可能导致 CARDINALITY 未更新或失效。

ANALYZE TABLE 是唯一标准手段,但它不保证立刻修好 Cardinality,尤其在大表、数据倾斜或配置不当的情况下。
为什么 ANALYZE TABLE 有时没效果
执行 ANALYZE TABLE 后发现 CARDINALITY 没变、执行计划照旧,常见原因有:
-
innodb_stats_persistent = OFF:统计信息只存内存,重启或自动更新触发后又回退;MySQL 5.7 及以前默认关,8.0+ 默认开 -
innodb_stats_on_metadata = OFF(5.7 及以前):连SHOW TABLE STATUS这类元数据查询都不会触发自动分析,手动ANALYZE TABLE成了唯一入口 - 采样页数太少:
innodb_stats_persistent_sample_pages默认 20,对超大表或分布极不均匀的字段(如 status=1 占 99%),20 页很可能全抽到同一值,估算基数直接崩成 1 - 表被长期锁定或 MVCC 版本链过长:在
READ-COMMITTED隔离级别下,ANALYZE TABLE可能卡在读取旧版本数据上,看似执行成功,实则采样失败
什么时候必须跑 ANALYZE TABLE
别凭感觉“定期跑”,盯住这几个信号再动手:
-
EXPLAIN显示type: ALL或key: NULL,但你知道该字段有高区分度索引(比如主键、唯一业务 ID) -
SHOW INDEX FROM table_name中某个索引的CARDINALITY是 0 或 1,而实际SELECT COUNT(DISTINCT col)返回几万行 - 慢查询日志里某条语句的
rows估算值比真实返回行数差 2 个数量级以上(例如估算 120 行,实际扫了 80 万行) - 刚执行完
INSERT ... SELECT、TRUNCATE + LOAD DATA或大批量UPDATE/DELETE,且后续查询明显变慢
怎么让 ANALYZE TABLE 更准、更安全
默认行为常不够用,关键参数和操作要主动干预:
- 对单次分析提精度:执行前临时调高采样页数,例如
SET GLOBAL innodb_stats_persistent_sample_pages = 100;,分析完再设回(注意:该变量动态生效,但仅影响后续分析) - 确认是否真更新了:不能只看命令返回
OK,必须跟一句SHOW INDEX FROM table_name查CARDINALITY是否变化 - 分区表要指定分区:
ANALYZE TABLE t PARTITION(p2024),否则默认只刷元数据,不碰实际数据页 - 避开高峰和长事务:它加表级读锁,会阻塞
DROP TABLE、ALTER TABLE,并发执行另一个ANALYZE TABLE也会等锁
ANALYZE TABLE 不是万能解药
如果调参+重分析后仍选错索引,问题大概率不在统计本身:
- SQL 写法导致索引失效:比如
WHERE DATE(create_time) = '2024-01-01'或WHERE city_id = '565'(隐式转换),ANALYZE TABLE完全无效 - 数据分布极端不均:即使采样翻倍,也可能连续抽到稀疏值段;这时得靠直方图(MySQL 8.0+ 支持,但需手动
ANALYZE TABLE ... UPDATE HISTOGRAM)或业务层拆分查询逻辑 - 优化器成本模型局限:比如回表代价预估偏低,导致宁可走二级索引也不走覆盖索引;这种得结合
FORCE INDEX临时兜底,再评估是否补覆盖索引
最麻烦的是:统计不准本身不报错、不告警,你得靠 EXPLAIN 和慢查日志主动“抓现行”。一旦忽略,它就在后台悄悄把所有依赖该索引的查询拖进性能泥潭。


















