登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  Redis

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 取三条。

Redis ZRANGE 以 Sorted Set、score 游标、BYSCORE 范围和 LIMIT 分页组成的静态关系说明图
图1:结构说明图,展示 Sorted Set、分值范围、游标和一页结果之间的关系,不是 Redis 控制台或运行截图。
# 先写入一组按 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 的成员重新纳入范围。

Redis ZRANGE 下一页使用排他游标并输出下一条成员的静态边界说明图
图2:边界说明图,展示上一页末尾、排他起点和下一页成员的静态关系,不是命令执行结果截图。
# 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写入端必须维持单调性
大量成员共享 scorescore + 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 更像深分页,不能获得同样稳定的推进语义。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>