rows是优化器基于统计信息估算的扫描行数,非实际扫描数或返回行数;受采样偏差、统计过期、数据倾斜、无直方图及列间相关性缺失影响,常显著偏离真实值。

EXPLAIN的rows根本就不是实际扫描数
rows是优化器基于统计信息“猜”出来的中间成本变量,不是执行时真实读取的行数。它不等于Rows_examined,也不等于最终返回的行数。比如你查一个时间范围,rows=5可能只是优化器从20个采样页里推断出“大概覆盖5条”,但若新数据全挤在最近1小时,实际扫描上千行很常见。
统计信息过期或采样太糙是主因
MySQL(尤其是InnoDB)默认只采样innodb_stats_sample_pages个叶子页(5.7/8.0默认都是20),再从中随机抽记录估算基数和分布。这意味着:
- 刚批量导入百万行后立刻
EXPLAIN,rows可能还卡在旧的几千行 - 表变更超10%才触发自动重算(
innodb_stats_auto_recalc=ON),且有延迟 - 两次
ANALYZE TABLE可能得出略有差异的cardinality,rows自然浮动 - 大表中20页采样常无法代表整体,尤其当高选择性字段(如唯一索引)的值集中在少数页时
直方图缺失放大非均匀分布的误差
MySQL 8.0+ 支持直方图,但默认不建。没有它,优化器对倾斜字段(如status字段95%是'active')完全无感知,一律按“均匀分布”估算:
-
WHERE status IN (0,1)占99%,优化器仍按等概率算,rows可能高估百倍 - 手动建需显式执行
ANALYZE TABLE t UPDATE HISTOGRAM ON status WITH 16 BUCKETS - 直方图只对
=、IN等简单谓词生效,BETWEEN或>基本不受益
复合条件与跨列相关性完全不建模
优化器把多条件独立估算再相乘,无视真实交集:
-
a=1 AND b=2:假设a=1返回1000行、b=2返回500行,就直接算1000×500=50万 - 但实际交集可能只有3行——因为
a和b强相关,而MySQL不维护列间关系统计 - 用
FORCE INDEX或覆盖索引时,rows有时反而更不准,因为它绕过了原本的索引选择路径
真正反映代价的是执行后的Handler_read_next、Handler_read_first等状态变量,或者慢日志里的Rows_examined。别拿rows当精确计数用,尤其在数据倾斜、未ANALYZE TABLE、复合查询场景下,偏差一个数量级是常态。











