You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CherryPy+Redis Web应用分页:选SQL Limit还是Redis Scan?

针对Redis+CherryPy分页的解决方案

嘿,先直接给你划重点:SQL Limit完全不适用,因为Redis是键值数据库,根本不支持SQL语法!你得用Redis的SCAN命令来实现分页,而且绝对不能用你现在写的con.keys()——数据量大的时候这个命令会直接阻塞Redis服务,生产环境里踩过这个坑的人都懂有多酸爽😅

为什么选Redis SCAN而不是KEYS?

  • KEYS *会一次性遍历Redis所有键,数据量几万以上时,Redis会被卡住好几秒,其他请求全堵着;
  • SCAN是渐进式遍历,每次只返回一小批键,不会阻塞服务,还能通过游标(cursor)控制遍历进度,完美适配分页需求。

怎么用SCAN实现每页10条的分页?

SCAN的基本用法是SCAN cursor COUNT 数量,游标从0开始,每次调用会返回新的游标和一批键,当游标回到0时表示遍历完成。不过要注意:COUNT只是Redis的提示值,不是精确返回数量,所以我们需要做一点处理来确保每页能拿到10条数据。

下面是修改后的CherryPy控制器代码,我帮你实现了分页逻辑:

import redis
import json
import cherrypy
from controllers.base import BaseController

con = redis.Redis(host='localhost', port=6379, db=3)

class HomeController(BaseController):
    @cherrypy.expose
    def index(self, page=1):
        page = int(page)
        per_page = 10
        total_collected = 0
        cursor = 0
        current_page_keys = []
        
        # 循环扫描直到凑够当前页的10条数据,或者遍历完所有键
        while total_collected < per_page * page:
            cursor, keys = con.scan(cursor=cursor, count=20)  # COUNT设为20提高命中率
            # 去重(SCAN可能返回重复键)
            unique_keys = list(set(keys))
            # 计算当前页需要的键范围
            start_idx = per_page * (page - 1)
            end_idx = start_idx + per_page
            
            # 把键加入临时列表
            current_page_keys.extend(unique_keys)
            total_collected = len(current_page_keys)
            
            # 如果游标回到0,说明遍历完了,直接跳出
            if cursor == 0:
                break
        
        # 截取当前页的10条键
        current_page_keys = current_page_keys[start_idx:end_idx]
        # 批量获取值,比循环get效率高很多
        values = con.mget(current_page_keys)
        
        # 处理数据,适配不同格式的值
        outdata = []
        for key, val in zip(current_page_keys, values):
            if val:
                try:
                    data = json.loads(val)
                    outdata.append({"key": key.decode('utf-8'), "value": data})
                except json.JSONDecodeError:
                    outdata.append({"key": key.decode('utf-8'), "value": val.decode('utf-8')})
        
        # 这里返回渲染后的页面(示例用模板引擎渲染,可根据你的框架调整)
        return self.render_template("index.html", data=outdata, current_page=page)

几个关键注意点

  • 重复键处理:SCAN可能会返回重复的键,所以用list(set(keys))去重(如果你的键是字符串格式,这个方法简单有效);
  • COUNT参数:设为比per_page大一点(比如20),避免每次返回的键太少,需要多次扫描;
  • 数据一致性:如果Redis中的键频繁增删,分页结果可能会出现重复或遗漏,这是SCAN的特性,没办法完全避免;如果对一致性要求极高,可能需要额外维护一个有序集合来存储键,但会增加复杂度;
  • 批量获取值:用mget代替循环get,能大幅提升性能,尤其是数据量大的时候。

内容的提问来源于stack exchange,提问作者Magnotta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 06:47:28