分页优化

分页的性能问题

排行榜上线后,用户反馈遇到了新问题:

【用户反馈】用户ID: 5678
问题:查看第 100 页时,加载非常慢

【用户反馈】用户ID: 1234
问题:排行榜有 10000 篇文章,但只能翻到第 500 页

【监控数据】
第 1 页:响应时间 50ms
第 10 页:响应时间 200ms
第 50 页:响应时间 2s
第 100 页:响应时间 8s
第 500 页:响应时间 30s+

问题:深分页性能差。

问题分析

传统分页的问题

性能问题:

ZREVRANGE 时间复杂度:O(log N + M)

N: 集合总大小
M: 返回元素数量

第 1 页:
- log N + M ≈ log 10000 + 20 ≈ 14 + 20 = 34

第 100 页:
- log N + M ≈ log 10000 + 20 ≈ 34
- 但需要先找到第 1980 个元素
- 实际复杂度更高

问题:
- Redis 仍需遍历到起始位置
- 深分页慢
- 大数据量时性能差

数据库分页的问题

如果使用数据库分页:

数据设计要点

  • 查询目标是快速定位状态、任务或资源,避免在关键路径上做大范围扫描。

性能问题:

OFFSET 的问题:
- 需要扫描前 1980 条记录
- 然后返回后 20 条
- 扫描的数据白白浪费

第 1 页:扫描 20 条
第 100 页:扫描 2000 条
第 500 页:扫描 10000 条

性能随页数增长而下降

优化方案

方案 1:限制最大页数

优点:

  • 实现简单
  • 性能可控

缺点:

  • 用户无法查看所有数据
  • 可能影响用户体验

方案 2:游标分页(推荐)

优点:

  • 性能稳定(每次查询都是 O(log N + M))
  • 支持无限翻页
  • 用户体验好

缺点:

  • 不能跳到指定页
  • 需要前端配合

方案 3:双向游标

方案 4:分片缓存

深分页优化

问题:数据库深分页

如果排行榜数据在数据库中,如何优化深分页?

数据设计要点

  • 查询目标是快速定位状态、任务或资源,避免在关键路径上做大范围扫描。

方案:使用覆盖索引

数据设计要点

  • 索引服务于高频查询,重点是缩小扫描范围,而不是堆更多字段。
  • 查询目标是快速定位状态、任务或资源,避免在关键路径上做大范围扫描。

性能对比:

传统方式:
- 扫描行数:2020 行
- 执行时间:约 500ms

覆盖索引方式:
- 扫描行数:20 行(只查索引)
- 执行时间:约 50ms
- 提升约 10 倍

前端优化

虚拟滚动

无限加载

性能对比

不同分页方案对比

场景:10,000 篇文章,查看第 100 页

传统 OFFSET 分页:
- 响应时间:2.5s
- 数据库扫描:2000 行
- 用户体验:差

游标分页:
- 响应时间:50ms
- 数据库扫描:20 行
- 用户体验:好
- 限制:不能跳页

分片缓存:
- 响应时间:10ms
- 数据库扫描:0 行(命中缓存)
- 用户体验:很好
- 限制:需要预热

推荐方案

建议:游标分页 + 分片缓存

前端:
- 使用游标分页
- 支持无限滚动
- 用户体验好

后端:
- 热门页面使用缓存
- 冷门页面使用游标查询
- 性能稳定

课后练习

练习 1

如何实现”快速跳转”功能(跳转到指定页)?

参考答案(3 个标签)
分页性能优化Redis

方案:使用 ZRANGEBYSCORE

练习 2

如何实现”每页大小可变”的分页?

参考答案(3 个标签)
分页灵活设计用户体验

方案:使用游标,支持动态页面大小

练习 3

如何处理”排名并列”的情况?

参考答案(3 个标签)
排行榜并列排名数据处理

方案:相同分数的处理

练习 4

如何实现”分页缓存”的自动刷新?

参考答案(3 个标签)
缓存自动刷新定时任务

方案:定时刷新缓存

练习 5

如何实现”分页预加载”?

参考答案(3 个标签)
分页预加载性能优化

方案:预加载下一页

思考题

  1. 如何实现”分页导出”功能(导出 Excel)?

  2. 如何处理”分页数据实时变化”的问题?

  3. 如何实现”多维度排序”的分页(按时间、按热度等)?

💡 提示:这些问题没有标准答案,建议结合实际情况深入思考。