Redis命令:HSCAN

Redis HSCAN 命令详细教程

HSCAN 以增量游标方式遍历 Hash 的字段与值,是遍历大 Hash 的标准手段。它每次调用只返回一部分数据,不会像 HGETALL 那样一次性阻塞服务端。

资料合集:https://pan.quark.cn/s/10e98d308913、https://pan.quark.cn/s/f56bc69c5338

一、概览与语法

redis 复制代码
HSCAN key cursor [MATCH pattern] [COUNT count] [NOVALUES]
项目 说明
数据类型 Hash
支持版本 Redis 2.8.0 起;NOVALUES 自 7.4 起
key 一个 Hash Key
cursor 游标,首次传 0,之后传上次返回的游标
MATCH pattern 可选,glob 风格模式过滤字段名
COUNT count 可选,每次迭代返回条数的提示值,默认 10
NOVALUES 可选,只返回字段名不返回值
时间复杂度 单次调用 O(1),完整遍历 O(N)
ACL @read、@hash、@slow
命令标记 readonly

官方元数据给出的复杂度说明是:每次调用 O(1),完成一次完整迭代(包括足够多次调用让游标回到 0)为 O(N),N 是集合中的元素数量。$TRAE_REF

二、返回值结构

返回一个二元数组:

位置 内容
第一个元素 游标,字符串形式的无符号 64 位数字
第二个元素 字段与值交替的数组;使用 NOVALUES 时只有字段名

游标返回 0 表示迭代结束。这不是"没有数据"的意思,而是"本轮遍历已完成"。必须继续调用直到游标为 0,否则会漏掉数据,这是使用 SCAN 系列最常见的错误。

游标本身是不透明的,不要对它做加减运算或假设其递增,只需原样回传。

三、基础示例

以下命令需要 Redis 2.8 或更新版本,在测试实例的 redis-cli 中执行。文中结果是预期说明,未实际连接 Redis 运行。

redis 复制代码
DEL tutorial:{hscan}:user
HSET tutorial:{hscan}:user name Alice city Shanghai age 30
HSCAN tutorial:{hscan}:user 0
HSCAN tutorial:{hscan}:user 0 COUNT 100
HSCAN tutorial:{hscan}:user 0 MATCH a* COUNT 100
HSCAN tutorial:{hscan}:user 0 NOVALUES COUNT 100
HSCAN tutorial:{hscan}:missing 0

预期结果:第一条 HSCAN 返回形如 1) "0" 2) 1) "name" 2) "Alice" ... 的二元数组,游标为 "0" 表示一次遍历即完成(Hash 很小)。COUNT 100 提高单次返回条数。MATCH a* 只返回 age 字段。NOVALUES 只返回字段名。对不存在的 Key 返回 1) "0" 2) (empty array),即游标为 0 且结果为空数组。

四、COUNT 只是提示,不是保证

COUNT 的默认值是 10,但它只是提示值(hint),不保证每次返回恰好这么多条。

常见误解 实际情况
COUNT 精确控制返回条数 只是提示,实际数量可能多于或少于
COUNT 决定遍历总次数 只能大致影响,不精确
COUNT 越大越好 越大单次阻塞时间越长,需要权衡
遍历必须一次拿到全部 必须循环调用直到游标为 0

当 Hash 使用紧凑编码(元素少且值小时),Redis 会在一次调用中返回全部元素并把游标置为 0,此时 COUNT 不起作用。当 Hash 转换为哈希表编码后,COUNT 才会明显影响每次返回的条数。

五、MATCH 是过滤而非筛选优化

MATCH 在服务端对已取出的元素做模式匹配,被过滤掉的元素仍然消耗了扫描成本。因此 MATCH 不能减少遍历的总工作量,只能减少返回给客户端的数据量。

注意事项 说明
模式语法 glob 风格,支持 *、?、[abc]、[a-z] 等
匹配对象 字段名,不是字段值
过滤时机 取出后过滤,不减少扫描量
结果完整性 被过滤掉的元素不会返回,但遍历仍需走完
大小写 区分大小写

如果需要对字段名做复杂筛选,可以在客户端过滤,避免在服务端做无谓的模式匹配。

六、遍历保证与限制

SCAN 系列提供的保证是有限的,理解这些限制才能正确使用:

保证 说明
完整遍历 从开始到结束一直存在于集合中的元素,一定会被返回至少一次
可能重复 同一元素可能被返回多次,客户端需自行去重
不保证不遗漏新增元素 遍历期间新增的元素可能返回也可能不返回
不保证快照 返回的是遍历过程中的实时状态,不是某一时刻的一致快照

因此 HSCAN 适合"遍历处理"而不是"精确统计"。需要精确字段总数应使用 HLEN,需要一致性快照应使用 HGETALL(但要评估规模)。

redis 复制代码
HSCAN tutorial:{hscan}:user 0 COUNT 10

如果返回的游标不是 "0",就必须把该游标作为下一次调用的参数继续执行,直到返回 "0" 为止。

七、客户端示例

前提为已安装 redis-py 并准备好本地测试实例。

python 复制代码
import redis

r = redis.Redis(host="localhost", port=6379, decode_responses=True)
k = "tutorial:{hscan}:python"
try:
    r.delete(k)
    r.hset(k, mapping={f"field{i}": f"value{i}" for i in range(100)})

    # 完整遍历:必须循环到游标为 0
    cursor = 0
    seen = {}
    while True:
        cursor, data = r.hscan(k, cursor, count=20)
        seen.update(data)
        if cursor == 0:
            break
    print(len(seen))    # 100

    # MATCH 过滤字段名
    cursor = 0
    matched = {}
    while True:
        cursor, data = r.hscan(k, cursor, match="field1?", count=50)
        matched.update(data)
        if cursor == 0:
            break
    print(sorted(matched)[:3])   # ['field10', 'field11', 'field12']

    # 只取字段名,不取值
    cursor, names = r.hscan(k, 0, count=100, no_values=True)
    print(len(names))   # 100
finally:
    r.delete(k)
    r.close()

Java(Jedis)示例,使用 ScanResult 与 ScanParams:

java 复制代码
try (Jedis jedis = new Jedis("localhost", 6379)) {
    for (int i = 0; i < 100; i++) {
        jedis.hset("tutorial:{hscan}:java", "field" + i, "value" + i);
    }
    String cursor = "0";
    ScanParams params = new ScanParams().count(20);
    Map<String, String> all = new HashMap<>();
    do {
        ScanResult<Map.Entry<String, String>> result =
                jedis.hscan("tutorial:{hscan}:java", cursor, params);
        for (Map.Entry<String, String> entry : result.getResult()) {
            all.put(entry.getKey(), entry.getValue());
        }
        cursor = result.getCursor();
    } while (!"0".equals(cursor));
    System.out.println(all.size());
    jedis.del("tutorial:{hscan}:java");
}

八、典型场景与性能建议

典型用途:遍历大 Hash 做数据导出或迁移、按前缀批量清理字段、定期巡检采样、避免 HGETALL 阻塞主线程。相比 HGETALL 和 HKEYS,HSCAN 把一次大开销拆成多次小开销,是线上处理大 Key 的推荐方式。

使用建议:

建议 说明
始终循环到游标为 0 否则会漏数据
客户端按字段名去重 遍历期间可能返回重复项
COUNT 取值适中 过小则往返次数多,过大则单次阻塞久
处理期间避免修改集合 增删可能导致部分元素重复或漏掉
不要依赖游标数值 游标不透明,只做原样回传
需要精确总数时用 HLEN HSCAN 计数不等于字段总数

九、练习、排错与总结

练习:新建 tutorial:{hscan}:exercise,写入 50 个字段;用游标循环完整遍历并统计收集到的字段数,确认与 HLEN 一致;再用 MATCH field1* 遍历,确认只匹配到预期字段;最后用 NOVALUES 遍历,确认返回值中只有字段名。

排错要点:只调用一次就停止会导致数据不全,务必循环到游标为 0;统计数量少于 HLEN 说明遍历未完成;出现重复字段属正常,应去重;MATCH 没匹配到结果时检查模式语法与大小写;NOVALUES 报错说明服务端版本低于 7.4;返回空数组且游标为 0 说明 Key 不存在。清理使用 DEL tutorial:{hscan}:user tutorial:{hscan}:missing tutorial:{hscan}:exercise。速记:2.8 起支持、游标循环到 0、COUNT 只是提示、MATCH 只过滤不省扫描、可能重复需去重、NOVALUES 自 7.4 起。

相关推荐
清水白石0082 小时前
Python 对象模型深度解析:从“一切皆对象”到 id、type、isinstance 底层机制与小整数缓存原理
开发语言·python·缓存
ly76893 小时前
Redis 分布式锁的边界条件:Redlock 争议、锁续期与客户端崩溃后的互斥失效
数据库·redis·分布式·分布式锁·watchdog·redlock
sinat_286945193 小时前
LLM Serving 中的四种缓存
人工智能·缓存·chatgpt
ShineWinsu5 小时前
对于Redis:RDB持久化的解析
linux·数据库·redis·缓存·面试·持久化·rdb
hweiyu0018 小时前
Redis命令:HTTL
redis·缓存
樱花落木兰1 天前
分布式登录实战:Session 会话共享改造,Redis 存储用户登录状态
java·javascript·数据库·redis·分布式·缓存
ShineWinsu1 天前
对于Redis:Steam、Geospatial、Hyperloglog、Bitmap、Bitfield类型的解析
数据库·c++·redis·分布式·缓存·面试·zset
海绵宝宝转agent1 天前
基于Redis ZSet+AOP+注解实现限流注解算法
数据库·redis·算法
imDwAaY1 天前
Redis 也能做消息队列?从 Stream 的存储讲到消费确认
数据库·redis·缓存