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 起。