深分页变慢因数据库需跳过大量数据;推荐游标分页、覆盖索引关联查询、物理分表、归档历史数据、前端限页、缓存预计算等优化方案。

深分页查询变慢,核心原因是数据库要先扫描并跳过前面大量数据,再返回目标页。比如 OFFSET 1000000 LIMIT 20,MySQL 或 PostgreSQL 都得逐行读取前 100 万条记录,即使有索引,也需回表或遍历索引树多次,I/O 和 CPU 开销剧增。
用游标分页(Cursor-based Pagination)替代 OFFSET
适合按时间、ID 等有序字段排序的场景,不依赖物理偏移量,而是“记住上一页最后一条的值”,下一页直接查大于该值的记录。
- 例如:按
created_at DESC, id DESC排序,上一页最后一条是created_at='2024-05-01 10:23:45', id=88721,下一页写法为:SELECT * FROM orders WHERE (created_at, id) - 必须给排序字段建联合索引(如
INDEX(created_at, id)),确保能高效定位起始位置 - 不支持跳转任意页(如直接跳到第 500 页),但对无限滚动、消息流等场景更稳定、更快
延迟关联 + 覆盖索引减少回表
当必须用 OFFSET 时,先用覆盖索引快速定位主键,再关联原表取完整字段,避免全字段扫描。
- 原始慢查询:
SELECT id, title, content FROM articles ORDER BY create_time DESC LIMIT 100000, 20; - 优化写法:
SELECT a.id, a.title, a.content FROM articles a INNER JOIN (SELECT id FROM articles ORDER BY create_time DESC LIMIT 100000, 20) b ON a.id = b.id; - 关键点:子查询只查
id(假设id在索引中),利用索引快速跳过,外层再按 ID 精准回查,大幅减少 I/O
物理分表或归档历史数据
如果深分页集中在老数据(如查 3 年前订单的第 1 万页),说明业务逻辑本身不合理——用户极少翻那么深,或数据未及时归档。
- 将 1 年前订单移入
orders_archive表,主表只保留热数据,深分页压力自然下降 - 按月/季度分表(如
orders_202401),配合路由规则,让查询落在小表上,OFFSET 成本显著降低 - 前端限制可翻页范围(如最多到第 200 页),后端配合返回友好提示:“数据过多,建议用搜索或筛选”
引入缓存或预计算结果
对访问频次高、排序固定、更新不频繁的列表(如热门商品榜、排行榜),避免每次实时分页计算。
- 用 Redis 有序集合(ZSET)预存 ID 列表,按 score 排序,
ZRANGE key start end WITHSCORES直接取页 - 定时任务每日凌晨生成分页快照表(如
top_products_page_100),字段含 page_no、item_ids(JSON 数组)、total_count - 注意一致性:数据变更时触发缓存更新或失效,避免展示陈旧结果










