Redis里分页查数据,快速浏览结果集的那些事儿怎么写才快
- 问答
- 2026-01-23 16:13:21
- 4
关于在Redis里快速分页查询数据这件事,关键在于理解Redis的特性,Redis很快,但它不是关系型数据库,没有像SQL里那种现成的“LIMIT offset, size”语法,如果你用错了方法,当数据量变大时,分页速度会变得非常慢,这里聊几种常见的做法和它们的优劣。
最直接能想到的方法,可能就是使用Redis的列表(List) 或者有序集合(ZSet),然后配合LRANGE或者ZRANGE这样的命令,这听起来很合理,因为这两个命令本身就支持偏移量(offset)和数量(count),很像分页,对于一个存储了文章ID的List,第一页用LRANGE article_ids 0 9,第二页用LRANGE article_ids 10 19,以此类推。
这种方法有一个致命的缺点,就是当页码越来越靠后时,速度会变慢,为什么?因为Redis的List是基于链表实现的(对于ZSet,其按排名访问也有类似特性),当你执行LRANGE article_ids 10000 10009时,Redis需要从链表头部开始遍历,走过10000个节点,才能找到第10001个节点开始返回数据,这个操作的时间复杂度是O(n),n是你的偏移量,如果有一百万条数据,取最后一页就需要遍历几乎整个链表,这显然是不可接受的,这种方法只适合数据量小,或者只浏览前几页的场景。(参考来源:Redis官方文档对LRANGE命令的说明)
那怎么解决这个“偏移量越大越慢”的问题呢?一个经典的优化方法是使用游标(Cursor) 分页,或者叫“seek method”(寻找法),它的核心思想是:不再使用页码和偏移量,而是记住上一次查询的最后一条记录的位置,然后从它后面开始取。
这在Redis里如何实现?有序集合(ZSet) 是这个方法的绝佳搭档,ZSet的每个成员都有一个分数(score),我们可以把分数作为排序和分页的依据,假设我们有一个文章ZSet,用发布时间戳作为分数。

- 第一页查询:使用
ZREVRANGEBYSCORE articles +inf -inf WITHSCORES LIMIT 0 10,意思是:从正无穷大到负无穷大(即从大到小)按分数排序,取前10条,这样就能得到最新的10篇文章和它们对应的时间戳。 - 获取下一页:关键是,我们要记录上一页最后一条文章的分数,假设最后一条文章的分数是
1640995200,那么下一页的查询命令就是:ZREVRANGEBYSCORE articles (1640995200 -inf WITHSCORES LIMIT 0 10,注意,分数区间这里用了(1640995200,这个圆括号表示开区间,意思是“小于1640995200”,这样,查询就会从分数小于1640995200的记录中,再取出最靠前的10条。
这个方法的好处是巨大的,无论你要取第几页,每次查询都只扫描固定的少量数据(10条),时间复杂度是O(log(n) + m),其中m是获取的数量,效率极高,完全避免了深度翻页的性能瓶颈。(参考来源:Redis官方文档对ZREVRANGEBYSCORE命令的说明,以及Redis之父Salvatore Sanfilippo在多篇文章中提及的游标分页思想)
除了基于ZSet的游标分页,还有一种更强大但也更复杂的工具——Redis Scan命令族(包括SCAN, SSCAN, HSCAN, ZSCAN),这个命令的设计初衷是为了替代KEYS命令(KEYS命令会一次性遍历所有键,在生产环境是危险的),但它天然支持一种迭代式的遍历,也很适合用来做某种意义上的“分页”。
SCAN命令的特点是:它每次执行只返回一小部分键和一个新的游标(cursor),你下次查询时带上这个游标,它就会从上次结束的地方继续扫描,它不像ZRANGE那样基于排序位置,而是基于Redis字典的哈希桶顺序,它不保证连续的两次查询之间没有数据插入或删除,即它不是强一致的,但它在整个遍历过程中能保证每个元素只被返回一次。

SCAN分页适用于哪些场景呢?它不适合需要严格排序和跳页的“前台”分页,比如用户一页一页地看商品列表,但它非常适合“后台”任务,比如需要分批处理所有数据、数据导出、批量过期key等,因为这些任务不关心顺序,只关心能否安全、无遗漏地处理全部数据。(参考来源:Redis官方文档对SCAN命令的说明,详细解释了其迭代原理和一致性保证)
还有一种思路是把分页逻辑前置到客户端,这在某些场景下非常高效,你的Redis里存储的是用户ID的集合(Set),现在要分页展示用户列表,你可以使用SMEMBERS命令一次性把所有用户ID取到客户端(前提是集合大小可控,比如不超过一万),在客户端的内存里进行分页计算,这样做,Redis端的压力只有一次网络通信和一个O(n)的命令,而后续的翻页操作完全在客户端进行,速度极快,缺点是首次加载可能稍慢,且需要客户端有足够的内存,并且无法感知服务端数据的实时变化。
在Redis里做快速分页,没有一刀切的最佳方案,要看具体需求:
- 需要排序且深度翻页:优先考虑基于ZSet分数的游标分页,这是最快最优雅的方式。
- 需要无序遍历全部数据(后台任务):使用Scan命令族。
- 数据量不大且翻页频繁:可以考虑一次性取出,客户端分页。
- 数据量小或只查前几页:用
LRANGE/ZRANGE带偏移量也可以接受。
核心就是避开大的偏移量,利用游标思想,让每次查询的代价是固定的,这样才能保证速度。
本文由畅苗于2026-01-23发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:http://waw.haoid.cn/wenda/84548.html
