Redis ZRANGE 怎样实现带游标的有序集合分页
来源:17golang原创
时间:2026-10-26 01:37:26 414浏览 收藏
Redis Sorted Set 如果用固定的 LIMIT offset count 做深分页,页码越靠后,服务端越需要跳过前面的成员;而且新增或删除成员后,后续页可能发生漂移。更稳妥的做法是把上一页最后一条记录的 score 当成游标,下一页用 ZRANGE ... BYSCORE 从这个分值之后继续取。
首屏从-inf开始,后续请求保存上一页最后一条数据的 score,并把它写成(last_score作为排他起点。若业务允许相同 score,必须让分页排序键唯一,或额外设计同分值的 tie-breaker,否则可能跳过同分值成员。
官方资料:https://redis.io/docs/latest/commands/zrange/
ZRANGE从 Redis 6.2 起可以用BYSCORE按分值查询,并配合LIMIT返回一页。- 下一页使用排他的
(last_score,避免把上一页最后一条再次返回。 - 游标分页解决的是“从哪里继续”,不是自动解决重复 score;生产数据要保证排序键可稳定推进。
先把首屏查询改成分值游标
假设有一个按发布时间排序的文章集合,成员是文章 ID,score 是已经构造好的递增排序值。首屏不需要知道页码,直接把分值范围写成 -inf 到 +inf,再用 LIMIT 0 3 取三条。

# 先写入一组按 score 排序的文章成员,score 在这个示例中是唯一递增值 redis-cli ZADD article:publish 1001 article-a 1002 article-b 1003 article-c 1004 article-d 1005 article-e # 首屏从负无穷开始,最多返回 3 个成员,并带回 score 供下一页使用 redis-cli ZRANGE article:publish -inf +inf BYSCORE LIMIT 0 3 WITHSCORES
首屏返回的最后一组值是 article-c 和 1003。真正需要交给客户端的不是“第 2 页”,而是这个边界值 1003。客户端可以把它放进下一次请求的 cursor 字段。
用排他边界推进下一页
下一页的关键是给起始 score 加一对圆括号:(1003 表示严格大于 1003。它与普通的 1003 不同,后者会把 score 等于 1003 的成员重新纳入范围。

# 1003 已经属于上一页,所以用 (1003 排除它,继续取后面的 3 条 # LIMIT 的 offset 从 0 开始,count 是本页最多返回的成员数 redis-cli ZRANGE article:publish (1003 +inf BYSCORE LIMIT 0 3 WITHSCORES
这样得到的是 article-d、article-e 等分值更大的成员。接口通常返回两部分:数据数组和下一次 cursor。下一次 cursor 应取当前页最后一条的 score;如果本页为空,则直接认为已经到达当前集合末尾。
重复 score 时,游标必须还能稳定推进
很多业务会把秒级时间戳直接当 score,这会让同一秒写入的多个成员共享分值。只保存 score 时,使用 (last_score 会一次性排除所有同分值成员,可能造成漏读;改成包含边界又会重复上一页数据。
一种简单做法是写入时构造唯一递增的排序值,例如用毫秒时间配合受控序号生成单调整数,并确保不会超过 Redis double 能精确表示的整数范围。另一种做法是把“score + member”都放进游标,在客户端或 Lua 逻辑中先处理同分值成员,再跳到更大的 score;这时要明确 member 的字典序规则,不能只凭字符串长度猜顺序。
| 数据条件 | 推荐游标 | 主要风险 |
|---|---|---|
| score 唯一递增 | 只保存最后一条 score | 写入端必须维持单调性 |
| 大量成员共享 score | score + member | 需要实现同分值的字典序推进 |
| 数据会频繁插入旧分值 | 固定排序规则与快照语义 | 新数据可能出现在已读区间,需明确是否接受 |
判断末页并控制查询成本
如果本页返回数量小于请求的 pageSize,通常可以把它作为“当前集合已读完”的信号;如果恰好等于 pageSize,则继续使用最后一条的 score 请求下一页。不要把返回空数组当成永久没有数据,因为集合可能在下一次请求前发生新增。
Redis 官方文档给出的 ZRANGE 复杂度是 O(log(N)+M),其中 M 是返回数量。游标分页把每次查询的结果量固定在 pageSize 附近,通常比不断增大的 offset 更适合长列表;但它仍然不等于数据库快照,跨请求的数据变化需要由业务决定是否允许重复、漏读或使用版本标记。
- 不要保存页码代替游标:页码无法表达数据在请求之间插入或删除后的真实位置。
- 不要忘记排他符号:没有
(时,边界成员会在下一页重复出现。 - 不要忽略 score 精度与同分值:排序值必须可稳定比较,必要时把唯一序号纳入排序模型。
总结这条链路:首屏用 -inf,后续页使用上一页末尾的排他 score,结果数量负责判断是否继续,写入模型负责保证游标不会跳过同分值成员。这样做,ZRANGE 才真正具备可交付的游标分页语义。
常见问题
为什么下一页要写成 (1003?
圆括号表示排他起点,下一页只取 score 严格大于 1003 的成员,可以避免上一页最后一条被重复返回。
score 相同能不能只保存最后一个 score?
不建议。排他 score 会跳过剩余同分值成员,包含 score 又会产生重复;应让排序值唯一,或把 member 的稳定顺序纳入游标。
ZRANGE 的 LIMIT 还能写 offset 吗?
可以,但在游标分页中通常固定为 0,把“从哪里开始”交给分值游标表达;较大的 offset 更像深分页,不能获得同样稳定的推进语义。
-
374 收藏
-
398 收藏
-
117 收藏
-
426 收藏
-
171 收藏
-
207 收藏
-
135 收藏
-
220 收藏
-
117 收藏
-
255 收藏
-
143 收藏
-
449 收藏
-
286 收藏
-
231 收藏
-
334 收藏
-
215 收藏
-
431 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习