mysql统计信息是抽样估算值而非精确计算,基于随机采样数据页推算基数与选择性;采样页数影响精度与性能;持久化开启时存于系统表,重启不丢失;自动更新依赖行变更比例估算,不实时;analyze table强制重采样并覆盖统计信息,适用于执行计划劣化场景;直方图可增强列值分布估算,需手动更新且依赖持久化;判断不准需结合实际执行表现与多源数据比对。

统计信息不是“算出来”的,而是“抽样估算”出来的
MySQL 的统计信息(比如 CARDINALITY、TABLE_ROWS)本质上是采样估算值,不是全表扫描后精确统计的。InnoDB 默认对每个索引随机抽取若干数据页(默认 innodb_stats_persistent_sample_pages=20),再基于这些页中键值的分布推算整个索引的基数和选择性。
- 采样页数越少,收集快但不准;越多越准但耗时长,尤其对大表可能卡住 DML
- 持久化开启(
innodb_stats_persistent=ON,默认)时,采样结果存到mysql.innodb_table_stats和mysql.innodb_index_stats表里,重启不丢 - 关闭持久化后,统计信息只存在内存,重启即清空,下次访问表或执行
SHOW TABLE STATUS会触发重新采样(若innodb_stats_on_metadata=ON) - 自动更新由
innodb_stats_auto_recalc控制:默认 ON,当表变更行数超过约 10% 时后台异步触发 —— 但这个“10%”是估算的计数器,不精确,且不会实时刷新
ANALYZE TABLE 干了什么?为什么有时要手动跑
ANALYZE TABLE 不是“重新计算所有行”,而是强制触发一次新的采样,并用结果覆盖当前统计信息(内存 + 持久化表)。它适用于自动更新没跟上、执行计划明显变差的场景,比如大批次 DELETE 或 INSERT 后。
- 对大表执行
ANALYZE TABLE会加MDL_SHARED_NO_WRITE锁,阻塞 DDL,但不阻塞普通 DML(如INSERT/UPDATE/SELECT) - 如果表有分区,
ANALYZE TABLE默认只分析一级分区元数据,不深入每个子分区(除非 MySQL ≥ 8.0.23 且显式指定) - 想跳过写 binlog(避免从库重复执行),可用
ANALYZE NO_WRITE_TO_BINLOG TABLE tbl - 不要在高峰期对几百 GB 的表频繁跑 —— 它会读取大量随机页,可能引发 I/O 尖峰
直方图(UPDATE HISTOGRAM)是统计信息的“增强包”
基础统计信息只提供全局基数,无法描述列值分布倾斜(比如 95% 的 status='active',5% 是其他值)。这时需要直方图:ANALYZE TABLE t UPDATE HISTOGRAM ON col_name WITH 16 BUCKETS。
- 直方图只影响优化器对
WHERE条件的选择性估算,不影响索引本身的CARDINALITY - 必须配合
innodb_stats_persistent=ON才能持久保存;否则每次重启丢失 - 桶数(
BUCKETS)不是越多越好:16–64 足够覆盖常见倾斜,超 256 容易拖慢EXPLAIN解析速度 - 直方图不自动更新 —— 数据大幅变化后,得手动再跑
UPDATE HISTOGRAM,否则形同虚设
怎么判断统计信息“不准”?别只看 EXPLAIN 的 rows
EXPLAIN 中的 rows 列只是估算值,本身不准不说明问题;关键是看实际执行是否走错索引、JOIN 顺序反常、或 type 突然变成 ALL。
- 对比
SELECT COUNT(*) FROM t和INFORMATION_SCHEMA.TABLES.TABLE_ROWS:差 3 倍以上就值得怀疑 - 查
SHOW INDEX FROM t,看CARDINALITY是否明显偏离真实唯一值数量(比如CARDINALITY=1但你知道该字段几乎全唯一) - 执行
EXPLAIN FORMAT=JSON SELECT ...,检查"rows_estimation"下各表的"row_count"是否严重偏离预期 - 注意:
INFORMATION_SCHEMA.STATISTICS的CARDINALITY是内存缓存值,可能滞后;真正权威的是mysql.innodb_index_stats里的n_diff_pfx01字段
TABLE_ROWS 和实际 COUNT,比等业务报慢更靠谱。










