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

Redis SCAN MATCH COUNT 怎样分批扫描业务键

来源:17golang原创

时间:2026-10-28 23:55:26 168浏览 收藏

按业务前缀分批扫描 Redis 键,可以从 SCAN 0 MATCH order:* COUNT 200 开始,下一次传入上次返回的游标,直到返回游标为 0。即使某轮没有匹配键,只要游标不是 0,就要继续;COUNT 是工作量提示,不是固定页大小。

官方地址:https://redis.io/docs/latest/commands/scan/

# 只读示例:引号防止shell把星号展开成本地文件。
# 响应第一部分是下一游标,第二部分才是本轮匹配键。
redis-cli SCAN 0 MATCH 'order:*' COUNT 200

这里使用单实例当前逻辑数据库中的键空间。命令没有读取键值,也没有删除键。示例数字只表示一个可调整的起点,不能据此承诺每轮固定处理量或延迟。

为什么扫描几轮就以为没有业务键了?

常见错误是把 SCAN 当分页接口:固定传入 0、自己给游标加 1,或者遇到空数组便退出。这几种写法都没有遵守 Redis 的游标迭代协议。MATCH 使用 glob 模式匹配键名,匹配结果稀疏时,多轮空结果是正常情况。

先检查实际连接的实例和数据库,再确认前缀及大小写。模式 order:* 不是正则表达式;如果业务键名不同,即使扫描完整,也可能没有匹配项。空批次只能说明这一轮没有返回匹配键,不能说明全库没有。

参数或响应作用不代表什么
游标继续本次迭代页码、偏移或完成百分比
MATCH按 glob 模式筛选键名独立业务前缀索引
COUNT提示单次扫描工作量返回条数上限或固定页大小
返回游标 0本轮迭代结束生成了一份时间点快照
SCAN请求参数和响应对象的静态结构
结构说明图:输入游标、MATCH 与 COUNT 分别承担不同职责;响应包含返回游标和键列表,空列表不是结束标志。

用一个完整循环修复提前退出

下面使用已安装 redis-py 的 Python 环境,直接连接本地独立测试实例的数据库 0。把它作为只读采集骨架;生产连接需按实际认证、TLS 和超时要求配置,凭据不应写进正文或输出文件。处理函数仅把键名编码成 JSON 行,不访问键值。

import base64
import json
import sys
import time
import redis

# 保留二进制键名,避免假设所有Redis键都能直接按UTF-8解码。
client = redis.Redis(
    host="127.0.0.1", port=6379, db=0,
    decode_responses=False,
    socket_connect_timeout=3, socket_timeout=3,
)
cursor = 0
calls = 0
returned = 0
deadline = time.monotonic() + 60
completed = False
exit_code = 0

try:
    while True:
        # 必须使用上一轮返回的cursor;不自行递增。
        cursor, keys = client.scan(
            cursor=cursor, match=b"order:*", count=200
        )
        calls += 1
        for key in keys:
            # 每行保存一个键名;Base64也能表示换行等特殊字节。
            print(json.dumps({
                "key_b64": base64.b64encode(key).decode("ascii")
            }))
            returned += 1

        # 先处理最后一批键,再判断完整迭代是否结束。
        if cursor == 0:
            completed = True
            break
        if time.monotonic() >= deadline:
            raise TimeoutError("达到扫描任务时间预算,结果尚不完整")
        time.sleep(0.02)  # 只给批次间加入小间隔,不保证服务器响应时长。
except (redis.RedisError, TimeoutError) as exc:
    print(str(exc), file=sys.stderr)
    exit_code = 1
finally:
    # 这是实际运行时生成的状态;本文没有伪造扫描统计值。
    print(json.dumps({
        "completed": completed, "last_cursor": cursor,
        "calls": calls, "returned_occurrences": returned
    }), file=sys.stderr)
    client.close()

raise SystemExit(exit_code)

这段代码把三类判断分开:键列表决定本轮要处理什么,游标决定迭代是否结束,时间预算决定任务是否应中断。达到预算会报“未完成”,而不是把已经拿到的一部分键冒充完整清单。预算在批次之间检查,单次网络等待仍受连接和读取超时约束,并非严格的总墙钟上限。

代码没有把全部键攒进内存,因此 returned_occurrences 统计的是返回次数,包含可能的重复,不是去重后的业务键总数。输出包含键名信息,应按业务数据敏感程度存放,不要直接发送到公开日志。

修好循环后,为什么还会看见重复或变化?

SCAN 允许同一键重复返回,处理端应去重或保证重复处理没有副作用。只读导出可在后续存储中按原始键字节去重;需要执行写操作时,仅靠扫描阶段去重并不足够,仍要处理键被改写、过期或重新创建的情况。

它也不是快照。完整扫描期间持续存在的匹配键有相应遍历保证,而期间新增、消失或变化的对象不能按某个固定时间点的集合理解。扫描结束后立即 GET,一个键也可能已经不存在;这不自动证明 SCAN 出错。

扫描结果重复处理和完成状态的静态责任关系
结构说明图:返回次数与唯一键清单是不同对象,完成状态由游标决定,中断状态保留部分结果的含义,不提供快照保证。

怎样调整 COUNT,才不会把分批变成大请求?

先记录每轮耗时和返回量,再结合 Redis 延迟与应用压力调整。COUNT 可以在同一次迭代的不同调用间改变,但下一轮游标仍必须来自上一轮。更高 COUNT 可能减少往返,也可能增加单次工作量;没有适合所有键空间的固定数字。

对于常规单实例扫描,不要把 MATCH 想成一个能直接定位所有前缀键的索引。业务只匹配少数键时,总扫描成本仍值得关注。如果需要高频精确查询某类对象,应考虑维护明确的业务索引,而不是不断重扫整个键空间。

本文代码不是 Redis Cluster 全集群枚举器。集群场景需根据拓扑和客户端行为明确每个节点的扫描范围及迁移期间的结果语义;不要把一个普通连接的游标跨节点续用,也不要把单节点完成误报成全局完成。

怎么核对扫描程序写对了?

在独立测试实例放入有限且保持稳定的已知键集合,既包含匹配前缀,也包含不匹配前缀。完整扫描后按键字节去重,与预设集合比较;同时记录空批次是否被跳过、最后一批是否处理,以及 completed 是否只在返回游标 0 时成立。本文不把这项读者测试建议当作已经执行的证据。

能用游标实现前端“上一页、下一页”吗?

不宜把它当稳定分页方案。键不保证业务排序,结果可能重复且没有快照语义;有排序与翻页要求时需要匹配的业务数据结构。

任务中断后保存游标就能永久可靠续扫吗?

游标应结合原实例、数据库和任务条件理解,不能当长期稳定的检查点。连接目标、服务状态或拓扑变化后应重新评估;需要可靠遍历任务时,保留重复处理能力,并明确重启扫描与未完成记录的策略。

参考:Redis SCAN 游标、COUNT 与迭代保证。

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