统计信息真过期表现为优化器选错执行路径,如索引未被使用或行数预估偏差巨大;需对比information_schema.statistics的cardinality与count(distinct)结果,而非依赖show index或show table status。

怎么判断统计信息真过期了?别只看 SHOW INDEX 里的 Cardinality
统计信息过期不是“看起来不对”,而是优化器实际选错了路——比如明明有索引,EXPLAIN 却显示 type: ALL;或者同样一条 SELECT COUNT(*),两次执行预估行数差十倍。这时候再查 information_schema.STATISTICS,发现 Cardinality 远低于真实去重数(比如状态字段只有 3 个值,却显示 Cardinality=1200),基本就能坐实。
- 别信
SHOW TABLE STATUS的Rows字段——它不实时更新,且默认采样极粗(尤其大表),偏差常超 50% - 更靠谱的检查方式:
SELECT table_name, cardinality FROM information_schema.STATISTICS WHERE table_schema = 'your_db' AND column_name = 'your_col';,再和SELECT COUNT(DISTINCT your_col) FROM your_table;对比 - 升级 MySQL 后特别容易出问题:8.0+ 默认关闭
log_queries_not_using_indexes,你以为慢日志开着,其实连“未走索引”的语句都不记——先确认:SHOW VARIABLES LIKE 'log_queries_not_using_indexes';必须是ON
ANALYZE TABLE 要不要跑?什么时候该用 FULLSCAN
ANALYZE TABLE 是最直接的修复手段,但它不是万能膏药。对小表(innodb_stats_persistent_sample_pages = 20)导致新统计依然不准。
- 大表慎用默认
ANALYZE TABLE:优先改配置:SET GLOBAL innodb_stats_persistent_sample_pages = 128;(需配合innodb_stats_persistent = ON) - 对分布极端不均的列(如
status字段 95% 是 1,其余是 2/3/4),必须加FULLSCAN:ANALYZE TABLE orders WITH SAMPLE 100 PERCENT; - 别在高峰期跑——它会阻塞 SELECT,错误日志里可能出现
Waiting for table flush
为什么自动更新经常“失灵”?关键开关得手动开
MySQL 默认开启统计信息自动更新,但触发条件太佛系:InnoDB 表要累计修改行数 ≥ 表当前行数 × 10% 才触发。一张 5000 万行的用户表,得改满 500 万行才更新——业务早崩了。
- 必须确认并启用持久化统计:
SHOW VARIABLES LIKE 'innodb_stats_persistent';返回ON,否则每次重启都回退到弱采样 - 禁用“懒加载”陷阱:
SET GLOBAL innodb_stats_on_metadata = OFF;(默认 ON,会导致SHOW TABLE STATUS触发临时分析,拖慢元数据查询) - 升级后最容易漏掉:旧配置文件里可能残留
innodb_stats_persistent = OFF或innodb_stats_auto_recalc = OFF,得手动覆盖
索引还在,但查询变慢?先查统计,再动 SQL
很多开发者一见慢就改 SQL、加索引、调参数,结果发现根本是优化器“看错了人”。比如 WHERE create_time > NOW() - INTERVAL 7 DAY 突然变慢,EXPLAIN 显示走了全表扫描——不是语句写错,而是统计里 create_time 的值域范围严重低估,让优化器以为“最近 7 天数据极少”,误判索引不划算。
- 典型信号:
Handler_read_key / Com_select比值骤降(比如从 0.9 掉到 0.1),说明索引命中率断崖下跌 - 修复顺序固定:
ANALYZE TABLE→ 观察EXPLAIN是否恢复 → 再考虑是否调整 SQL 或索引结构 - 最易忽略的坑:分区表的统计信息是按分区单独维护的,
ANALYZE TABLE不会自动扫所有分区,得显式ANALYZE TABLE t PARTITION(p202401, p202402);
统计信息这事,表面是命令一敲就完,背后全是时间窗口、采样精度、引擎行为的咬合。很多人修了一次就以为一劳永逸,结果两周后又掉坑里——因为没把 innodb_stats_persistent_sample_pages 调上去,也没监控 information_schema.INNODB_TABLESTATS 里的 last_update 时间戳。











