Redis的哈希类型非常适合存储对象结构的数据,比如一个用户信息可以存成user:1001这个键下包含name、age、city等多个字段。当我们需要知道某个哈希键里到底有哪些字段时,HKEYS命令是最直接的答案。它会返回指定键中所有的字段名,本文将从语法、性能、替代方案等多个角度把这个命令讲透。

HKEYS命令基本语法与返回值
HKEYS的语法非常简单,只需要一个参数,即目标哈希键的键名:
HKEYS key
如果键存在且是哈希类型,命令返回一个包含所有字段名的数组;如果键不存在,返回空数组;如果键存在但不是哈希类型,则会返回一个WRONGTYPE错误。这一点在实际开发中要特别注意,尤其是当一个键可能被其他模块以字符串或列表类型写入时,调用前最好先用TYPE命令确认类型。
下面用一个完整的示例演示HKEYS的执行效果:
HSET user:1001 name "张三" age 28 city "北京" HKEYS user:1001 # 返回结果 1) "name" 2) "age" 3) "city" HKEYS not_exist_key # 返回空数组 (empty array)
返回的字段顺序是不保证的,Redis内部使用ziplist或 hashtable两种编码存储哈希,当字段数量较多时底层会转换为hashtable编码,此时字段顺序与写入顺序没有对应关系。所以业务代码不要依赖HKEYS返回的顺序做逻辑判断,如果需要有序结果,应该在客户端自行排序。
HKEYS、HVALS与HGETALL三者对比
围绕哈希类型的整体读取,Redis提供了三条命令,它们的关系可以通过下表清晰对比:
| 命令 | 返回内容 | 典型场景 |
|---|---|---|
| HKEYS | 所有字段名 | 只关心结构,不需要值 |
| HVALS | 所有字段值 | 只关心数据,不需要字段名 |
| HGETALL | 字段名和值交替返回 | 需要完整对象数据 |
三者的时间复杂度都是O(N),N为哈希中的字段总数。选择哪一条命令完全取决于业务需要:如果只需要判断某个对象有哪些属性,用HKEYS可以避免传输大量值数据,减少网络带宽占用;如果要把整个对象反序列化成Java或Python对象,HGETALL更合适。
举个例子,在用户画像系统中,如果只是想展示用户资料包含哪些维度,HKEYS返回的纯字段名列表就足够了,数据量小、传输快。而缓存整个商品详情页时,字段和值都要用,HGETALL一次取回更省事。盲目使用HGETALL取全量数据,是很多初学者容易踩的坑。
大哈希键的性能风险与SCAN渐进式替代方案
HKEYS最大的隐患在于它的时间复杂度是O(N)并且是阻塞执行的。当哈希键中存储了上百万元素时,一次HKEYS会让Redis主线程花费大量时间遍历,期间所有其他请求都被阻塞,严重时直接引发线上故障。这和KEYS命令在全键空间的问题是一样的原理。
生产环境中判断是否可以用HKEYS,先要看数据规模。一般来说,字段数在几千以内的哈希键直接使用HKEYS没有问题;超过十万级别就应该考虑改用HSCAN命令做渐进式遍历。HSCAN专门为大集合设计,它通过游标机制分批返回数据,每次只取一小部分,不会长时间阻塞Redis。
用Python客户端演示HSCAN的用法:
import redis
r = redis.Redis(host='127.0.0.1', port=6379, decode_responses=True)
cursor = 0
all_fields = []
while True:
cursor, data = r.hscan('big_hash_key', cursor=cursor, count=500)
# data是dict,取keys即字段名
all_fields.extend(data.keys())
if cursor == 0:
break
print('字段总数:', len(all_fields))这里的count参数是提示值,告诉Redis每次大概扫描多少个元素,实际返回数量可能略有出入,但整体上把一次大遍历拆成了多次小遍历,每次耗时可控。Java中使用Jedis或Lettuce也有对应的hscan方法,用法思路完全一致。
还有一种常见的架构层面做法是维护一个辅助集合。比如用SET单独记录哈希键的所有字段名,写入时同步维护,读取字段列表时改用SMEMBERS或SSCAN,将遍历压力从大哈希上转移出来。这种方案增加了一致性维护成本,适合字段列表读取频率远高于写入频率的场景。
各语言客户端调用示例
不同的Redis客户端对HKEYS的封装略有差异,但接口都很直观。Python的redis-py示例如下:
import redis
r = redis.Redis(host='127.0.0.1', port=6379, decode_responses=True)
r.hset('product:2001', mapping={'title': '手机', 'price': '3999', 'stock': '120'})
fields = r.hkeys('product:2001')
print(fields) # 输出 ['title', 'price', 'stock']Java中Jedis的写法:
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.hset("product:2001", "title", "手机");
Set<String> fields = jedis.hkeys("product:2001");
for (String field : fields) {
System.out.println(field);
}注意decode_responses参数,Python客户端默认返回bytes类型,加上这个参数后直接返回字符串,省去手动解码的麻烦。另外这些命令在集群模式下都能正常使用,因为它们只操作单个键,不涉及跨槽访问的问题。
小结
HKEYS是一个简单但需要谨慎使用的命令。小规模哈希键上它是获取字段名列表的最佳选择,而大规模数据场景下务必切换到HSCAN渐进式遍历,避免阻塞Redis主线程。理解HKEYS与HVALS、HGETALL的分工,结合业务实际选择合适的读取命令,才能让Redis在项目中发挥稳定高效的性能。