mysql执行limit offset, size时并非跳转到指定行,而是从头逐行扫描并计数,先处理offset+size行(全部经历索引查找、回表、排序),再丢弃前offset行,仅返回后续size行,导致io、内存和cpu开销随offset线性增长。

因为 MySQL 必须扫描并加载 offset + limit 行数据,再丢弃前 offset 行——被跳过的行照样要走索引查找、回表、排序全流程。
MySQL 执行 LIMIT offset, size 的真实步骤
不是“跳到第 N 行”,而是“从头数 N 行”。以 LIMIT 1000000, 20 为例:
- 从主键或排序索引起点开始,逐行扫描,每行都计数
- 扫描满 1000000 行后,才开始取后续 20 行
- 这 1000020 行全部经过存储引擎 → Server 层流程:索引定位 → 回表(如需)→ 排序(如无法用索引排序)→ 裁剪
- 前 1000000 行虽不返回,但已消耗 IO、内存和 CPU
为什么 EXPLAIN 显示 rows = offset + size 却没报错?
EXPLAIN 中的 rows 字段是预估扫描行数,它反映的就是这个“先扫再丢”的代价。常见误解是以为 Using index 就代表快,但:
-
Using index只说明用了覆盖索引,避免了回表,但扫描行数不变 - 如果
SELECT *或非索引字段被选中,就会触发回表,而回表次数 = offset + size - 实测显示:offset 从 10 万升到 100 万,回表耗时可能暴涨 4–10 倍
ORDER BY + LIMIT 组合让问题更隐蔽
当排序字段无有效索引,或索引无法满足排序+查询条件时,MySQL 会:
- 先用 WHERE 条件筛选出结果集(可能全表扫描)
- 再对整个中间结果集排序(使用
Using filesort) - 最后应用 LIMIT —— 此时排序已完成,offset 依然要跳过大量已排序行
- 换句话说:
LIMIT是最后一步,无法下推到扫描或排序阶段
真正影响响应时间的是扫描基数,不是页码本身
第 10001 页(OFFSET 100000)慢,不是因为“页码大”,而是因为数据库必须处理 100020 行。几个关键事实:
- offset 每增加 10 倍,执行时间近似线性增长(非指数,但足够致命)
- 当 offset > 10000,应默认视为高风险查询,需强制干预
- 即使加了缓存,深分页请求仍会穿透到 DB,且难以命中(参数组合多、热点分散)
最容易被忽略的一点:游标分页(WHERE id > ? ORDER BY id LIMIT 20)能彻底绕过 offset,但它要求排序字段严格单调、不可重复、有索引支撑——缺一不可。










