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')完全无感知:
- 它按“所有值均匀分布”估算,
WHERE status = 'inactive'的rows可能被高估几十倍 - 手动创建需显式执行:
ANALYZE TABLE t UPDATE HISTOGRAM ON status WITH 16 BUCKETS - 直方图只对
=、IN等简单谓词有效;BETWEEN、>基本不受益
复合条件更不准:优化器不会算交集
优化器对多条件组合没有跨列统计,也不建模逻辑关系:
-
a = 1 AND b = 2:它分别估算a = 1返回 1000 行、b = 2返回 500 行,再相乘得 50 万——但真实交集可能只有 3 行 - 用
FORCE INDEX或覆盖索引时,rows有时反而更离谱,因为它绕过了原本的索引选择路径,统计信息没适配新路径 - 真正反映实际开销的是
Handler_read_first、Handler_read_next等状态变量,不是rows
最常被忽略的一点:即使你刚跑完 ANALYZE TABLE,只要查询里带函数(WHERE DATE(create_time) = '2026-09-01')、隐式转换或模糊前缀(LIKE '%abc'),优化器就会直接放弃使用索引统计,rows 就变成拍脑袋的粗略估算——这时候看 rows 已毫无意义。











