EXPLAIN 中的 rows 估算不准是因为优化器依赖非实时统计信息;数据大量变更后需手动 ANALYZE TABLE 更新索引基数、列分布等元数据,否则导致执行计划劣化。

为什么 EXPLAIN 里的 rows 总是离谱?
因为 MySQL(或 PostgreSQL)的查询优化器靠统计信息估算数据分布,而这些统计信息不是实时的——它不看实际数据,只看采样或缓存的元数据。一旦表数据大量变更(比如批量导入、删旧增新),rows 就会严重偏离真实扫描行数,导致选错索引、用错连接顺序,甚至退化成全表扫描。
实操建议:
- 别信刚建完表或刚导入数据后的
EXPLAIN结果,先ANALYZE TABLE - 对大表(>100 万行),默认采样可能不足;MySQL 5.7+ 可调
innodb_stats_persistent_sample_pages提高精度 - PostgreSQL 要注意
default_statistics_target,默认 100 不够用,高频查询字段可单独ALTER TABLE ... ALTER COLUMN ... SET STATISTICS 500 -
ANALYZE TABLE不锁表(InnoDB),但会短暂加 MDL 读锁;线上大表避开高峰期
ANALYZE TABLE 到底分析了什么?
它不是“检查数据是否正确”,而是收集:索引的基数(Cardinality)、列值分布直方图(MySQL 8.0+/PG)、空值比例、平均长度等。这些直接决定优化器对 WHERE a = ? 或 JOIN 条件的选择性判断。
常见错误现象:
- 执行
ANALYZE TABLE t1后SHOW INDEX FROM t1发现Cardinality没变 → 实际已更新,但SHOW INDEX显示的是缓存值,查information_schema.STATISTICS或重启连接才可见 - 对只有主键的表反复
ANALYZE,rows估算仍不准 → 主键本身无法提供非主键字段的分布信息,必须分析具体列或建二级索引 - 分区表只
ANALYZE了某一分区,但优化器按全局统计估算 → MySQL 分区表需显式ANALYZE TABLE t1 PARTITION(p2024),否则只更新元数据
什么时候必须手动 ANALYZE TABLE?
自动更新不可靠:MySQL 默认只在表变更量超 1/16 或启动时触发,PostgreSQL 的 autovacuum analyze 依赖 pg_stat_all_tables.n_mod_since_analyze,但阈值容易滞后。
以下场景务必手动执行:
- 批量插入 > 表总行数 10% 后(比如从 50 万涨到 60 万)
- 执行过
TRUNCATE或大量DELETE(尤其没走索引的条件) - 上线新索引后首次跑关键查询前
- 发现某条慢查询的
EXPLAIN中rows比SELECT COUNT(*)小两个数量级 → 统计严重过期
注意:ANALYZE TABLE 不会重写数据文件,也不影响 MVCC 版本链,但会触发统计信息持久化写入磁盘(InnoDB)或系统表(PG),有少量 I/O。
如何验证统计信息真的更新了?
不能只看 ANALYZE 命令是否成功返回 OK,得查底层数据。
MySQL:
- 查
information_schema.STATISTICS中对应索引的CARDINALITY字段变化 - 对比
SHOW INDEX FROM t1和SELECT * FROM information_schema.STATISTICS WHERE TABLE_NAME='t1'—— 后者更准 - 执行
EXPLAIN FORMAT=JSON SELECT ...,看"rows_examined_per_scan"是否明显下降
PostgreSQL:
SELECT tablename, attname, n_distinct, most_common_vals FROM pg_stats WHERE tablename = 't1';- 重点看
n_distinct(去重数)是否接近真实值,most_common_vals是否覆盖高频值 - 用
\d+ t1查表详情,末尾显示Statistics updated:时间戳
最直接的验证方式:同一个查询,ANALYZE 前后跑两次 EXPLAIN,对比 rows 和 key 字段变化。如果没变,大概率分析被跳过了(比如表为空、权限不足、或正在被其他 DDL 阻塞)。
统计信息不是越新越好,也不是越多越好——直方图精度太高反而拖慢 ANALYZE 本身,而低频小表过度分析纯属浪费。关键是让优化器在关键路径上看到靠谱的数字。










