Redis 核心数据结构(三)——Hash,把一堆字段塞进一个 Key

对象无需再存 JSON 字符串了,Hash 让你直接改里面的某个字段,不用全量覆盖。

本次导航

  • Hash 长什么样(像极了 Python 的字典)
  • 核心命令:HSETHGETHGETALLHINCRBY
  • 内部编码:什么时候省内存,什么时候变哈希表
  • 哈希字段过期(Redis 7.2+ 的 HEXPIRE,告别整 Key 过期)
  • 实战场景:用户信息、对象缓存、购物车
  • Hash vs String 存 JSON:谁更香?

发车前提醒:先玩熟 String 和 List,Hash 就在它们之间。

一、Hash 就是 Redis 里的"小字典"

在一个 Redis Key 下面,再挂一堆 field-value 对。很像编程语言里的字典或者对象。

bash 复制代码
HSET user:1001 name "张三" age 25 city "北京"

这样,user:1001 这个 Key 下,有三个字段:nameagecity。你可以单独改 age,不用把整个对象重新存一遍。

和 String 存 JSON 的区别

  • String 存 JSON:改一个字段,你得取出整个 JSON,反序列化,改完再序列化,再 SET 回去。
  • Hash:直接 HSET user:1001 age 26,一步到位。

二、核心命令

命令 功能
HSET key field value [field value ...] 设置一个或多个字段
HGET key field 获取指定字段
HGETALL key 获取所有字段和值(生产环境慎用,字段多会慢)
HMGET key field [field ...] 获取多个字段
HDEL key field [field ...] 删除一个或多个字段
HEXISTS key field 判断字段是否存在
HINCRBY key field increment 字段值增加指定整数
HINCRBYFLOAT 增加浮点数
HLEN key 获取字段数量
HKEYS / HVALS 获取所有字段名 / 所有值

动手试试

bash 复制代码
# 塞一个用户
HSET user:1001 name "李四" age 30 balance 1000

# 单独取年龄
HGET user:1001 age          # "30"

# 给余额加 200
HINCRBY user:1001 balance 200   # 返回 1200

# 同时获取多个字段
HMGET user:1001 name balance    # ["李四", "1200"]

# 看看都有哪些字段
HKEYS user:1001     # ["name","age","balance"]

# 删除年龄字段
HDEL user:1001 age
HGETALL user:1001   # 只剩 name 和 balance

注意HGETALL 当字段非常多(比如几百上千)时,可能会阻塞 Redis。生产环境多用 HMGET 取你真正需要的字段。

powershell 复制代码
127.0.0.1:6379> HSET user:1001 name "李四" age 30 balance 1000
(integer) 1
127.0.0.1:6379> HGET user:1001 age
"30"
127.0.0.1:6379> HINCRBY user:1001 balance 200
(integer) 1200
127.0.0.1:6379> HMGET user:1001 name balance
1) "\xe6\x9d\x8e\xe5\x9b\x9b"
2) "1200"
127.0.0.1:6379> HKEYS user:1001
1) "name"
2) "age"
3) "city"
4) "balance"
127.0.0.1:6379> HDEL user:1001 age
(integer) 1
127.0.0.1:6379> HGETALL user:1001
1) "name"
2) "\xe6\x9d\x8e\xe5\x9b\x9b"
3) "city"
4) "\xe5\x8c\x97\xe4\xba\xac"
5) "balance"
6) "1200"

三、内部编码

Hash 底层有两种存储方式:

编码 条件 说明
listpack 字段和值都比较小,数量少 连续内存,省空间
hashtable 超过配置阈值(默认512字节或64个字段) 转为真正的哈希表,读写 O(1)

你可以用 OBJECT ENCODING key 查看:

bash 复制代码
HSET small a 1 b 2
OBJECT ENCODING small   # "listpack"

# 塞一个长字符串,超过阈值
HSET big c "为给定哈希键的一个或多个字段设置过期时间(TTL 或生存时间)。您必须至少指定一个字段。字段的 TTL 到期时将自动从哈希键中删除。字段的过期时间仅会被删除或覆盖哈希字段内容的命令清除,包括 HDEL 和 HSET 命令。这意味着所有在概念上修改哈希键字段值而不替换为新值的操作都不会影响 TTL。您可以使用 HPERSIST 命令清除 TTL,该命令会将哈希字段变回持久字段。请注意,使用零 TTL 调用 HEXPIRE/HPEXPIRE 或使用过去的 Unix 时间调用 HEXPIREAT/HPEXPIREAT 将导致哈希字段被删除。HEXPIRE 命令支持一组选项:NX -- 对于每个指定字段,仅当字段没有过期时间时设置过期时间。XX -- 对于每个指定字段,仅当字段已存在过期时间时设置过期时间。GT -- 对于每个指定字段,仅当新的过期时间大于当前过期时间时设置过期时间。LT -- 对于每个指定字段,仅当新的过期时间小于当前过期时间时设置过期时间。对于 GT 和 LT 选项,非易失性字段被视为具有无限 TTL。NX、XX、GT 和 LT 选项是互斥的。您可以将已设置 TTL 的字段作为参数调用 HEXPIRE。在这种情况下,生存时间将被更新为新值。" 
OBJECT ENCODING big     # "hashtable"

不用你操心,Redis 自动升级底层的存储方式。

powershell 复制代码
127.0.0.1:6379> object encoding small
"listpack"
127.0.0.1:6379> object encoding big
"hashtable"

四、哈希字段过期(Redis 7.4+ 的神器)

在老版本里,整个 Hash 要么一起活,要么一起死。你不能让 user:1001temp_token 字段 5 分钟失效,而 name 永久保留。

Redis 7.4 引入了字段级过期,命令如下:

bash 复制代码
HEXPIRE key seconds [NX | XX | GT | LT] FIELDS numfields field [field ...]

参数说明

  • key: 哈希键名称。
  • seconds: TTL(以秒为单位)。
  • FIELDS numfields field [field ...]: 指定字段及其数量。

可选参数

  • NX: 仅当字段没有过期时间时设置。
  • XX: 仅当字段已有过期时间时设置。
  • GT: 新的过期时间必须大于当前过期时间。
  • LT: 新的过期时间必须小于当前过期时间。

示例:

bash 复制代码
# 为不存在的键设置过期时间
HEXPIRE no-key 20 NX FIELDS 2 field1 field2
(nil)
# 创建一个哈希键并设置字段
HSET examplekey field1 "hello" field2 "world"
(integer) 2
# 为字段设置 10 秒的过期时间
HEXPIRE examplekey 10 FIELDS 3 field1 field2 field3
1) (integer) 1 # field1 的过期时间成功设置
2) (integer) 1 # field2 的过期时间成功设置
3) (integer) -2 # field3 不存在,返回 -2
# 检查哈希键内容
HGETALL mykey
(empty array)

画个图,更直观的了解下:

场景 :用户登录后存 session_id 在 Hash 里,设置 30 分钟过期,同时保留用户基本资料。以前你要单独存一个 String Key,现在一个 Hash 搞定。

五、实战场景

场景1:用户信息 / 对象缓存

bash 复制代码
# 存用户资料
HSET user:1001 name "王五" email "wang@example.com" points 100

# 增加积分
HINCRBY user:1001 points 50

# 修改邮箱(只改一个字段)
HSET user:1001 email "newwang@example.com"

# 取出来展示
HGETALL user:1001

场景2:购物车(经典案例)

每个用户的购物车是一个 Hash,field 是商品 ID,value 是数量:

bash 复制代码
# 用户1001 添加 2个商品101,1个商品102
HSET cart:1001 prod_101 2
HINCRBY cart:1001 prod_102 1

# 增加某种商品数量
HINCRBY cart:1001 prod_101 1   # 现在变成3

# 减少数量
HINCRBY cart:1001 prod_101 -1

# 删除商品
HDEL cart:1001 prod_102

# 查看购物车所有商品
HGETALL cart:1001

购物车天然适合 Hash,商品数量可以单独增减,不需要序列化整个数组。

场景3:配置项 / 属性存储

比如存某个服务的开关配置:

bash 复制代码
HSET config:service_a timeout 30 retry 3 enable_ssl 1
HGET config:service_a timeout

随时改单条配置,不用重写整个配置对象。

六、Hash vs String 存 JSON:对决

假设你要存一个用户对象:{"name":"张三","age":25,"city":"北京"}

方式 存数据 改 age 读 age 内存占用
String + JSON SET user:1001 '{"name":"...","age":25,...}' 取出→反序列化→改→序列化→SET GET 后解析 序列化后的字符串
Hash HSET user:1001 name "张三" age 25 city "北京" HSET user:1001 age 26 HGET user:1001 age 每个 field 单独存,但压缩后更省

结论

  • 如果你经常单独修改某个字段,Hash 完胜。
  • 如果你每次都整体读写(比如整个对象传给前端),String + JSON 可能更方便。
  • 内存方面,小对象 Hash 的编码非常省;大对象两者差别不大。

推荐:对象字段少、频繁单独改 → Hash。对象大、嵌套深、整体读为主 → String + JSON。


📢 点关注,不迷路

如果你看到这儿了,欢迎 点赞 + 关注 ,我们下期见~

相关推荐
故事和你917 小时前
洛谷-算法2-4-字符串2
开发语言·数据结构·c++·算法·深度优先·动态规划·图论
cpp_25017 小时前
P3374 【模板】树状数组 1
数据结构·c++·算法·题解·洛谷·树状数组
郝学胜-神的一滴7 小时前
干货版《算法导论》 02 :算法效率核心解密
java·开发语言·数据结构·c++·python·算法
枕星而眠7 小时前
C语言数组专题:从一维到二维,吃透内存与指针
java·数据结构·算法
Maiko Star7 小时前
Spring AI 多轮对话记忆(ChatMemory)保姆级教程:从内存版到 Redis 持久化
java·redis·spring
Thanks_ks7 小时前
穿透海量数据的迷雾:深入理解布隆过滤器的架构哲学与工程权衡
redis·高并发·缓存穿透·架构设计·布隆过滤器·分布式系统·海量数据
木木_王7 小时前
嵌入式Linux学习 | 数据结构 (Day04)链表升级(进阶优化 + 柔性数组原理 + 双向循环链表完整实现 + 高频面试深挖)
linux·数据结构·学习
papership16 小时前
【入门级-数据结构-3、特殊树:完全二叉树的数组表示法】
数据结构·算法·链表
smj2302_7968265216 小时前
解决leetcode第3911题.移除子数组元素后第k小偶数
数据结构·python·算法·leetcode