mysql优化器仅在能确定结果集已满且后续行不可能入选时才提前终止扫描;否则limit仅最后裁剪,不改变执行计划。

MySQL 优化器对 LIMIT 的提前终止是否真的“跳过扫描”?
不是所有带 LIMIT 的查询都会提前终止扫描。优化器只在**能确定结果集已满、且后续行不可能进入最终结果**时,才可能停止读取。比如 ORDER BY + LIMIT 配合索引时,它会用索引有序性逐条取、够了就停;但若没合适索引,或用了 DISTINCT/GROUP BY,它仍得扫完临时表或全表——LIMIT 只是最后裁剪,不参与执行计划生成。
常见错误现象:EXPLAIN 显示 rows 很大,但实际执行很快;或相反,rows 小却很慢。这是因为 rows 是预估扫描行数,不反映 LIMIT 是否触发了运行时终止。
- 有覆盖索引 +
ORDER BY indexed_col LIMIT N→ 通常能用索引下推,取满 N 行即停 -
WHERE unindexed_col = ? ORDER BY id LIMIT N→ 先过滤再排序,LIMIT对扫描无帮助,必须找完所有匹配行才能排序取前 N -
SELECT DISTINCT col FROM t LIMIT 10→ 必须去重完成才能知道哪 10 个,无法提前终止
LIMIT 在子查询里会不会被下推到内层?
MySQL 8.0.22+ 对部分场景支持 LIMIT 下推(如派生表、IN 子查询),但不是默认行为,也不保证生效。优化器是否下推,取决于是否能保持语义等价——比如外层 ORDER BY 和内层排序冲突,就会禁用下推。
使用场景:想加速 SELECT * FROM t1 WHERE id IN (SELECT id FROM t2 ORDER BY created_at DESC LIMIT 100) 这类关联,别指望内层自动加 LIMIT;得显式写成派生表并加提示,或改用 JOIN。
- 显式派生表 +
FORCE INDEX或USE INDEX可提高下推概率 -
SELECT ... FROM (SELECT ... LIMIT 100) AS dt比裸子查询更可能触发物化+限制 - MySQL 5.7 基本不下推,子查询先全量执行,
LIMIT无效 - 错误信息如
Subquery cache disabled due to LIMIT表示优化器主动规避了缓存,但不等于下推成功
分页偏移量大时(LIMIT 1000000, 20)为什么慢?
因为 MySQL 仍要定位到第 1000001 行——即使不返回前面的数据,索引树或主键 B+ 树也得逐层遍历、跳过 100 万条记录。这不是“读 100 万行”,而是“走 100 万次索引指针跳转”,IO 和 CPU 开销都在线性增长。
性能影响:偏移量每翻倍,响应时间几乎同步翻倍(尤其无缓存、高并发时)。InnoDB 的聚簇索引顺序读在这里毫无优势。
- 用游标分页替代:记录上一页最大
id,查WHERE id > ? ORDER BY id LIMIT 20 - 避免
SQL_CALC_FOUND_ROWS,它强制全扫,和LIMIT矛盾 - 如果必须用偏移,确保
ORDER BY字段有单独索引,且不包含NULL(否则索引可能失效) -
pt-query-digest报告中出现大量LIMIT N,M且M固定、N递增,基本就是游标没切过去
如何确认某条 LIMIT 查询是否被优化器真正“短路”?
不能只看 EXPLAIN,得结合 optimizer_trace 和执行时的 Handler_read_* 状态变量。核心指标是 Handler_read_next(索引顺序读次数)是否接近 LIMIT 值,而不是远大于它。
实操步骤:
- 执行前开追踪:
SET optimizer_trace="enabled=on"; - 跑查询,再查
SELECT * FROM information_schema.OPTIMIZER_TRACE,搜"rows_estimation"和"rerun_with_limit"字段 - 执行后查状态:
SHOW STATUS LIKE 'Handler_read%';,重点对比Handler_read_next和你设的LIMIT值 - 如果
Handler_read_next ≈ LIMIT,说明大概率短路了;若差 10 倍以上,基本是全扫后裁剪
容易被忽略的是:InnoDB 层的“短路”和 Server 层的“短路”不是一回事。Server 层拿到够的行就停,但 InnoDB 可能已经预读了更多页——所以 innodb_buffer_pool_reads 有时仍很高。










