EXPLAIN的rows是优化器基于过时、采样粗糙、无直方图和跨列统计的估算值,并非真实扫描行数;它受统计延迟、数据倾斜、函数/模糊查询导致统计失效等影响,偏差常见。

EXPLAIN 的 rows 是优化器基于过时、粗糙、无上下文的统计信息“猜”出来的数字,不是真实扫描数——别拿它当真,尤其在数据倾斜或批量写入后。
为什么 rows 总是不准?统计信息本身就不新鲜
MySQL(尤其是 InnoDB)不会实时更新索引基数、值分布等统计信息。ANALYZE TABLE 才是唯一能强制重采样的操作,而默认情况下:
- 大批量导入 100 万行后,
rows可能还卡在旧的 5 万 -
innodb_stats_auto_recalc默认开启,但只在表变更超 10% 时才触发,且有延迟 - 刚建完一个高选择性索引,
SHOW INDEX里Cardinality仍是 0 或极低——因为采样没落到对应页
采样太糙:20 页 vs 几千万行
InnoDB 默认只采样 innodb_stats_sample_pages 个叶子页(5.7/8.0 默认都是 20),每页再随机抽几条记录。这意味着:
- 大表中,20 页可能只覆盖 0.01% 的实际数据分布
- 两次
ANALYZE TABLE可能得出明显不同的Cardinality,rows自然浮动 - 调高到
innodb_stats_sample_pages = 100能缓解,但耗时上升明显,且仍无法解决本质问题
分布盲区:没有直方图,就等于瞎猜
MySQL 8.0+ 支持直方图,但默认不创建。没有它,优化器对非均匀字段(如 status 字段 95% 是 'active')完全无感知:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 它按“所有值均匀分布”估算,
WHERE status = 'inactive'的rows可能被高估几十倍 - 手动创建需显式执行:
ANALYZE TABLE t UPDATE HISTOGRAM ON status WITH 16 BUCKETS - 直方图只对
=、IN等简单谓词有效;BETWEEN、>基本不受益
真正反映实际开销的是 Handler_read_first、Handler_read_next 等状态变量
rows 是优化器预测“可能需要检查多少行”,但它把索引查找和回表分离估算,忽略多条件组合的真实选择率:
-
a = 1 AND b = 2:它分别估算a = 1返回 1000 行、b = 2返回 500 行,再相乘得 50 万——但真实交集可能只有 3 行 - 用
FORCE INDEX或覆盖索引时,rows有时反而更离谱,因为它绕过了原本的索引选择路径,统计信息没适配新路径 - 查询里带函数(
WHERE DATE(create_time) = '2026-09-01')、隐式转换或模糊前缀(LIKE '%abc'),优化器直接放弃使用索引统计,rows就变成拍脑袋的粗略估算
最常被忽略的一点:即使你刚跑完 ANALYZE TABLE,只要查询里带函数、隐式转换或模糊前缀,看 rows 已毫无意义。真正影响性能的,是执行后的 Rows_examined 和 Handler_read_*——这些才是你该盯住的数字。










