Redis HSCAN 命令详细教程
HSCAN以增量游标方式遍历 Hash 的字段与值,是遍历大 Hash 的标准手段。它每次调用只返回一部分数据,不会像 HGETALL 那样一次性阻塞服务端。
资料合集:https://pan.quark.cn/s/10e98d308913、https://pan.quark.cn/s/f56bc69c5338
一、概览与语法
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 运行。
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(但要评估规模)。
HSCAN tutorial:{hscan}:user 0 COUNT 10如果返回的游标不是"0",就必须把该游标作为下一次调用的参数继续执行,直到返回"0"为止。
七、客户端示例
前提为已安装 redis-py 并准备好本地测试实例。
importredis 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}"foriinrange(100)})# 完整遍历:必须循环到游标为 0cursor=0seen={}whileTrue:cursor,data=r.hscan(k,cursor,count=20)seen.update(data)ifcursor==0:breakprint(len(seen))# 100# MATCH 过滤字段名cursor=0matched={}whileTrue:cursor,data=r.hscan(k,cursor,match="field1?",count=50)matched.update(data)ifcursor==0:breakprint(sorted(matched)[:3])# ['field10', 'field11', 'field12']# 只取字段名,不取值cursor,names=r.hscan(k,0,count=100,no_values=True)print(len(names))# 100finally:r.delete(k)r.close()Java(Jedis)示例,使用 ScanResult 与 ScanParams:
try(Jedisjedis=newJedis("localhost",6379)){for(inti=0;i<100;i++){jedis.hset("tutorial:{hscan}:java","field"+i,"value"+i);}Stringcursor="0";ScanParamsparams=newScanParams().count(20);Map<String,String>all=newHashMap<>();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 起。