rows是优化器基于统计信息估算的扫描行数,非实际执行时的真实扫描量;真实值需通过explain analyze(mysql≥8.0.18)查看actual rows,或监控handler_read_*状态变量。

rows是优化器的估算,不是执行时的真实计数
MySQL 的 EXPLAIN 中 rows 列代表优化器基于统计信息“猜”出来的扫描行数,它不等于实际读取的行,也不等于返回结果集大小。这个值只参与成本计算,用于比较不同执行路径哪个“看起来更便宜”。真实扫描量得看 Handler_read_next、Handler_read_first 等状态变量,或者用 EXPLAIN ANALYZE(MySQL ≥ 8.0.18)查 actual rows。
统计信息过期或采样失真最常见
InnoDB 不维护精确行数,而是随机采样 innodb_stats_persistent_sample_pages 个叶子页(默认 20),再推算整张表的分布。以下情况会让采样严重跑偏:
- 大批量写入/删除后没运行
ANALYZE TABLE,比如刚导入 200 万行,rows还显示旧的 5 万 - 字段值高度倾斜,例如
status字段 95% 是'done',但采样页全抽到'pending',导致对'done'的查询预估行数低得离谱 -
innodb_stats_persistent = OFF(老版本默认),统计只存在内存里,MySQL 重启就丢 - 表刚从备份恢复或
mysqldump导入,未触发自动分析
SQL写法直接让统计失效
有些写法会让优化器彻底放弃使用索引统计,退化为粗略估算甚至硬编码值(如 rows=1),这时调大采样页数也没用:
- WHERE 条件对索引列用了函数:
WHERE DATE(create_time) = '2026-01-01'、WHERE UPPER(name) = 'A' - 隐式类型转换:
WHERE user_id = '123'(字段是INT)或WHERE code = 123(字段是VARCHAR) - 联合索引缺失最左前缀:
INDEX (a,b,c),但查WHERE b = 1 AND c = 2 - LEFT JOIN 后在 WHERE 对右表加非空条件:
LEFT JOIN orders ON u.id = o.user_id WHERE o.status = 'paid',逻辑上已变成 INNER JOIN,但优化器可能误判选择率
真正该盯住的不是rows,而是它为什么失准
看到 rows=1 但慢日志里 Rows_examined 是 50 万,别急着改 innodb_stats_persistent_sample_pages。先确认三件事:
- 用
EXPLAIN FORMAT=JSON查used_columns和key_parts,看 WHERE 是否命中索引列 - 检查 JOIN 字段类型是否一致(比如
BIGINTvsVARCHAR)、ON 条件有没有函数 - 查
SHOW INDEX FROM table_name,确认Cardinality是否明显偏离实际唯一值数量
rows 失准本身不可怕,可怕的是把它当真——尤其在数据倾斜、复合条件、范围查询场景下,偏差一个数量级太常见了。











