limit offset在大数据量下必然变慢,因其执行时需逐行扫描并丢弃前offset行,扫描行数≈offset+limit,导致io和cpu开销随offset线性增长。

LIMIT OFFSET 本身无法“高效”处理深分页,它在 OFFSET 超过几千后必然变慢——这不是写法问题,而是 MySQL 必须逐行跳过前 M 行的执行机制决定的。
为什么 LIMIT OFFSET 在大数据量下会越来越慢
MySQL 执行 LIMIT 10 OFFSET 100000 时,并不会直接定位到第 100001 行。它实际做了三件事:全量扫描(或索引扫描)→ 按 ORDER BY 排序 → 从头开始计数,丢弃前 100000 行 → 再取 10 行。整个过程扫描行数 ≈ OFFSET + LIMIT。
- EXPLAIN 显示
rows字段远大于你想要的条数,就是最直接的证据 - 哪怕只查
SELECT id FROM t LIMIT 1,只要OFFSET是 500000,MySQL 仍要“数”完 50 万行 -
ORDER BY字段没索引?那还要额外触发Using filesort,雪上加霜
什么时候还能用 LIMIT OFFSET
它只在以下场景真正安全:
- 单表数据量 OFFSET )
- 后台导出类功能中,明确限制最大页码(如拒绝
OFFSET > 5000的请求) - 查询字段全部命中覆盖索引,且
ORDER BY字段有对应前缀索引(例如WHERE status = 1 ORDER BY created_at DESC,索引为(status, created_at)) - 前端仅提供“上一页/下一页”,不开放跳转任意页输入框
必须换方案的三个信号
一旦出现以下任一情况,LIMIT ... OFFSET ... 就该被替代:
- 慢查询日志里反复出现
OFFSET >= 50000的语句 - 用户可自由输入页码(比如管理后台的“跳至第 N 页”),且 N 可达 10000+
- 单表行数 > 100 万,且分页接口平均响应时间 > 800ms
此时应立即切换为游标分页:WHERE id > ? ORDER BY id LIMIT 20 或复合条件 WHERE (created_at, id) 。注意:游标分页要求排序字段组合必须唯一、单调,且前端需保存上一页末尾值。
最容易被忽略的细节
LIMIT 20, 10 和 LIMIT 10 OFFSET 20 完全等价,MySQL 内部都转成同一执行逻辑——改写语法不解决性能问题。
OFFSET 值不能是表达式(如 OFFSET @page * 20),否则预编译可能失效,执行计划无法复用。
用 WHERE id > N LIMIT 20 替代时,若主键有删除空洞(比如删过 id=123456),会导致漏数据;必须搭配严格递增且无删减的字段(如自增 id)或使用时间+主键双条件防漂移。











