explain 的 rows 与实际扫描差异大是因为索引基数未及时更新,优化器基于过时统计生成执行计划;常见于大表频繁写入却未 analyze table 或 innodb_stats_auto_recalc=off。

为什么 EXPLAIN 显示的 rows 和实际扫描差很多
因为 MySQL 的索引基数(cardinality)没及时更新,优化器基于过时统计做执行计划。常见于大表写入频繁但长期没 ANALYZE TABLE,或开启了 innodb_stats_auto_recalc=OFF。
- 触发时机:只有在表数据变更超过 10%(默认阈值)且
innodb_stats_auto_recalc=ON时,InnoDB 才自动更新统计;否则全靠手动 - 影响范围:只影响优化器对索引选择、连接顺序、是否走索引的判断,不影响查询结果正确性
- 查看当前基数:
SHOW INDEX FROM table_name中的Cardinality列,注意它只是估算值,不是精确计数
什么时候必须手动执行 ANALYZE TABLE
不是“定期跑一下就安心”,而是有明确信号才该动——比如执行计划突变、慢查询陡增、新加索引后没被用上。
- 刚批量导入/删除大量数据后(尤其 >5% 行数)
- 重建或新增索引后(
ALTER TABLE ... ADD INDEX不会自动触发统计更新) -
SELECT COUNT(*)和EXPLAIN预估行数偏差超过一个数量级(例如预估 100 行,实际扫 50 万) - 确认
innodb_stats_persistent = ON(推荐),否则统计重启后丢失,每次都要重算
ANALYZE TABLE 会锁表吗?线上能直接跑吗
取决于存储引擎和 MySQL 版本。InnoDB 在 5.6+ 默认只加 MDL 读锁(允许并发 DML),但仍有风险。
- MySQL 5.7+ / 8.0:
ANALYZE TABLE对 InnoDB 是轻量操作,不阻塞写入,但会短暂阻塞 DDL(如ALTER TABLE) - MyISAM:会锁整个表,禁止任何读写,线上严禁执行
- 大表耗时长(尤其无
innodb_stats_persistent_sample_pages优化时),可能持续几秒到几分钟,建议避开高峰 - 可加
TIMEOUT控制(MySQL 8.0.23+):ANALYZE TABLE tbl_name WITH TIMEOUT 30
怎么让基数统计更准,又不用总手动 ANALYZE
靠调参比靠频率更可靠。默认采样页数太小(innodb_stats_persistent_sample_pages=20),大表误差明显。
- 增大采样页数:
SET GLOBAL innodb_stats_persistent_sample_pages = 100(上限 2000,权衡精度与耗时) - 确保持久化开启:
SET GLOBAL innodb_stats_persistent = ON,避免重启丢统计 - 避免用
information_schema.TABLES查TABLE_ROWS估算——那是 MyISAM 风格的粗略值,InnoDB 下基本不准 - 监控
INFORMATION_SCHEMA.INNODB_INDEX_STATS表里的last_update字段,比查SHOW INDEX更准
基数统计本质是概率采样,再准也做不到 100% 匹配实时分布。真正卡住性能的,往往是索引设计本身不合理,而不是统计差那么一两万行。











