深度分页变慢的根本原因是MySQL需扫描offset+size行后丢弃前offset行;推荐游标分页(如WHERE id > 1234567 ORDER BY id LIMIT 20)或覆盖索引+延迟关联优化。

为什么深度分页(如 LIMIT 1000000, 20)会变慢?
MySQL 的 LIMIT offset, size 在深度分页时性能急剧下降,根本原因在于:数据库仍需从头扫描 offset + size 行数据,再丢弃前 offset 行,只返回最后 size 行。当 offset 达到百万级,即使有索引,全表扫描或索引遍历的 I/O 和 CPU 开销也极大,且无法利用索引的“跳转”能力。
用“游标分页”(Cursor-based Pagination)替代 OFFSET
核心思路:不依赖行号偏移,改用有序字段(如主键 id 或时间戳 create_time)的**上一页末尾值**作为下一页起点,实现无状态、可跳跃的查询。
- ✅ 前提:分页字段必须有唯一、非空、有索引(推荐主键或带唯一约束的自增/时间字段)
- ✅ 查询写法示例(按 id 升序分页):
SELECT * FROM orders WHERE id > 1234567 ORDER BY id LIMIT 20;
(1234567 是上一页最后一条记录的 id) - ⚠️ 注意:不能混用 ASC/DESC;若需倒序翻页,应使用 id < last_id + ORDER BY id DESC
覆盖索引 + 延迟关联优化大 offset 场景
若业务强依赖 offset(如后台管理需跳转任意页码),可在保留 LIMIT 用法的同时大幅提速:
- 第一步:仅用索引字段查出主键(避免回表),例如:
SELECT id FROM orders WHERE status = 1 ORDER BY id LIMIT 999980, 20; - 第二步:用这些 id 关联原表取完整数据:
SELECT o.* FROM orders o INNER JOIN (上面子查询) t ON o.id = t.id; - ✅ 关键点:WHERE 条件和 ORDER BY 字段必须落在同一复合索引中(如 INDEX(status, id)),确保子查询走索引扫描而非全表
其他实用建议
- 前端禁用“跳转到第 N 页”输入框,改为“下一页/上一页”+“加载更多”按钮,天然适配游标分页
- 对实时性要求不高的列表(如日志、归档数据),可预生成分页映射表(如 page_map(page_no, min_id, max_id)),用空间换时间
- PostgreSQL 用户可直接用 cursor + FETCH NEXT,MySQL 8.0+ 支持窗口函数,也可用 ROW_NUMBER() 配合 CTE 实现更灵活的逻辑分页(但慎用于超大表)











