深分页优化必须绕开OFFSET扫描逻辑,首选游标分页(用上一页末尾值过滤),次选延迟关联(先查主键再JOIN);二者均依赖ORDER BY字段的索引支撑,否则无效。

LIMIT 和 OFFSET 本身无法优化深分页——它们只是暴露问题的“开关”,不是解法。真正有效的优化必须绕开 OFFSET 的扫描丢弃逻辑。
为什么 OFFSET 越大越慢?
MySQL 执行 LIMIT 10 OFFSET 1000000 时,不是直接跳到第 1000001 行,而是:先按 ORDER BY 排序完整结果集 → 逐行扫描并计数 → 跳过前 1000000 行 → 再取 10 行。扫描行数 = OFFSET + LIMIT,500 万偏移时实际读取 500 万 + 行,I/O 和 CPU 双重压力。
游标分页(推荐首选)
用“上一页末尾值”代替“跳过 N 行”,彻底消除偏移扫描。前提是排序字段有索引且可比较(如 id 或 updated_at):
- 主键升序分页:上一页最后
id是 6800000,下一页写SELECT * FROM t WHERE id > 6800000 ORDER BY id LIMIT 20 - 时间倒序分页(
ORDER BY updated_at DESC):上一页最后updated_at是'2026-04-10 15:22:33',下一页写SELECT * FROM t WHERE updated_at - 时间不唯一时(同秒多条),必须叠加主键防漏/重:
WHERE updated_at - 禁止用
WHERE id > N LIMIT 20代替OFFSET,除非确认id连续无删除——现实业务中几乎不成立
延迟关联(适合需跳转任意页码的场景)
当产品必须支持“输入页码跳转”(如后台管理),无法用游标时,用两步减少回表成本:
- 第一步只查主键(走覆盖索引):
SELECT id FROM t WHERE type = 8 ORDER BY id LIMIT 1000000, 20(毫秒级) - 第二步用这些
id关联原表:SELECT a.* FROM t a JOIN (SELECT id FROM t WHERE type = 8 ORDER BY id LIMIT 1000000, 20) b ON a.id = b.id - 关键约束:子查询里只能选
id;a.id必须有索引(主键天然满足);不能在子查询里写SELECT *
索引与字段设计是前提,不是可选项
再好的游标或延迟关联,没有底层索引支撑就失效:
-
ORDER BY字段必须建索引,否则WHERE updated_at 会全表扫描 - 避免在排序字段上用函数:
ORDER BY DATE(created_at)会让索引失效 - 主键优先用自增整型,UUID 等无序值会导致 B+ 树频繁分裂,范围查询变慢
- 可考虑后端限制最大页码(如拒绝
OFFSET > 100000的请求),比硬扛更实际
游标分页看似要改前端逻辑(传 last_id 而非 page_num),但这是唯一能稳定支撑千万级数据、毫秒级响应的路径。延迟关联是妥协方案,而所有“调大 sort_buffer_size”“加 USE INDEX 提示”之类操作,对深分页瓶颈毫无作用——因为问题不在内存排序,而在磁盘上不得不扫过的那一百万行。


















