迁移后统计信息不准会导致优化器选错索引或全表扫描,因mysql.innodb_table_stats中n_rows、CARDINALITY等值未更新甚至为0;需先通过EXPLAIN rows预估偏差、SHOW INDEX CARDINALITY与COUNT(DISTINCT)对比、排除函数/隐式转换及确认innodb_stats_persistent=ON来确诊,再分步修复:启用持久化、调大采样页数、必要时重建索引并验证last_update与n_rows。

迁移后统计信息不准,90%以上会直接导致优化器选错索引或走全表扫描——这不是SQL写错了,也不是索引建漏了,而是mysql.innodb_table_stats里存的n_rows、CARDINALITY这些值根本没更新,甚至为0。
怎么确认是统计信息问题,不是SQL或索引本身的问题
别一慢就跑ANALYZE TABLE。先做三件事:
- 查执行计划:
EXPLAIN SELECT * FROM your_table WHERE indexed_col = 'val';,看rows预估是否离谱(比如实际几百行,却显示rows=2000000) - 查索引基数:
SHOW INDEX FROM your_table;,重点看CARDINALITY列——若为0或远低于SELECT COUNT(DISTINCT indexed_col) FROM your_table;的结果(差一个数量级),基本坐实统计失效 - 排除干扰项:确认没用函数(如
WHERE DATE(created_at) = '2024-01-01')、没隐式转换(INT列传字符串)、且innodb_stats_persistent = ON(否则统计不落盘,ANALYZE完重启就丢)
为什么迁移后统计信息容易崩
迁移过程本身就会切断统计信息的连续性:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
-
mysqldump导入默认不触发自动统计更新,尤其用了--skip-extended-insert或分段导入时,innodb_stats_auto_recalc阈值根本没被触达 - 物理拷贝(如直接复制
.ibd)后,INNODB_TABLESTATS表里的记录完全丢失,CARDINALITY清零,但表结构和数据看起来“一切正常” - 跨版本迁移(如5.7 → 8.0)时,
innodb_stats_persistent_sample_pages默认值变化,旧采样逻辑失效,新统计又没主动触发 -
FLUSH TABLES WITH READ LOCK类备份操作会重置stat_modified_counter,导致后续自动更新永远不触发——迁移脚本里藏得最深的坑
修复动作必须分两步:先重建元数据,再刷新统计
光跑ANALYZE TABLE不够,很多场景下它压根不生效:
- 先确保统计持久化开启:
SET GLOBAL innodb_stats_persistent = ON;,并写入配置文件,否则下次重启归零 - 大表或数据倾斜严重时,默认采样页数(约10–20页)精度不足,需临时加大:
SET GLOBAL innodb_stats_persistent_sample_pages = 100;,再执行ANALYZE TABLE your_table; - 如果迁移后发现
CARDINALITY = 0且ANALYZE无效,检查是否因字符集变更(如utf8→utf8mb4)导致索引被截断失效——此时要先重建索引:ALTER TABLE your_table DROP INDEX idx_name, ADD INDEX idx_name (col(191));,再ANALYZE - 验证是否真生效:查
SELECT database_name, table_name, n_rows, last_update FROM mysql.innodb_table_stats WHERE table_name = 'your_table';,对比last_update时间戳和n_rows是否与实际接近
别指望一次修复管半年
统计信息不准不会报错,也不会告警,它安静地让查询从1秒变1分钟,直到某天突然超时。真正能防住它的,只有两件事:把ANALYZE TABLE加入低峰期巡检脚本;监控information_schema.STATISTICS.CARDINALITY的波动——比如某张核心表的CARDINALITY一天内跌了90%,那八成已经出问题了。










