EXPLAIN的rows值在LIMIT查询中不靠谱,因其是优化器基于统计信息的粗略估算,而非实际扫描行数;加LIMIT后优化器误判提前终止成本,忽略数据离散性、回表开销与ORDER BY重复值导致的不确定性,真实扫描量须以Handler_read_next为准。

为什么EXPLAIN的rows值在LIMIT查询里不靠谱
因为EXPLAIN输出的rows列是优化器“估算”的扫描行数,不是实际执行时读取的行数。加了LIMIT后,优化器会假设“我只要找到够数就停”,于是大幅压低估算值——哪怕它实际得扫100万行再丢掉999990条。你看到rows=10,不代表只读了10行;它可能读了100万行,只是“计划里打算只用10条”。
LIMIT让优化器误判索引选择成本
优化器在评估索引时,会把LIMIT当作“提前终止信号”,从而高估某些索引的价值。比如有(created_at, id)联合索引,又带WHERE user_id = ?,优化器可能觉得:“走这个索引+回表验证,命中第一条就停,代价小”。但它没算准:如果满足user_id的记录在时间上高度离散(比如用户早期下过单、近期又下了单),那它就得沿着created_at倒序一路回表,直到翻到某条匹配的user_id——中间可能回表上万次。
- 这种误判在
WHERE条件区分度低(如user_id重复率高)+ORDER BY字段与过滤字段无强相关性时特别明显 - 真实代价来自随机I/O(回表)、CPU判断(每行都要检查
user_id是否匹配),而优化器只粗略按“索引深度×1.2”估算
排序字段存在重复值时,优化器完全放弃确定性推断
当ORDER BY列有大量重复值(比如status只有3个枚举值),优化器无法保证“第N条”对应唯一物理位置。它会退化成保守策略:要么强制走全表扫描+文件排序(为确保顺序稳定),要么选一个看似“快”的索引但实际要反复跳转验证。这时候LIMIT不仅没帮上忙,反而锁死了更优路径——比如本可走PRIMARY KEY覆盖扫描,却因LIMIT诱导选了二级索引+回表。
- MySQL官方文档明确说:多行
ORDER BY值相同时,返回顺序是nondeterministic - 这意味着优化器连“哪条是第一条”都不敢赌,只能扩大扫描范围保正确性
真正要看的不是EXPLAIN,而是Handler_read_next
想确认实际读了多少行,别信rows,直接查Handler_read_next变量:
SHOW SESSION STATUS LIKE 'Handler_read_next';
执行SQL前后各查一次,差值才是真实遍历的索引行数。这个数字不会骗人——它反映的是引擎层真实调用的“下一行”次数。很多线上慢查询优化失败,就是因为只盯着EXPLAIN改索引,却没发现Handler_read_next暴涨了两个数量级。
复杂点在于:优化器的成本模型是静态的、基于统计信息的,而你的数据分布、缓存状态、并发负载都是动态的。它算得再细,也替代不了你亲手看Handler变量那一眼。


















