对象无需再存 JSON 字符串了,Hash 让你直接改里面的某个字段,不用全量覆盖。
本次导航
- Hash 长什么样(像极了 Python 的字典)
- 核心命令:
HSET、HGET、HGETALL、HINCRBY - 内部编码:什么时候省内存,什么时候变哈希表
- 哈希字段过期(Redis 7.2+ 的
HEXPIRE,告别整 Key 过期) - 实战场景:用户信息、对象缓存、购物车
- Hash vs String 存 JSON:谁更香?
发车前提醒:先玩熟 String 和 List,Hash 就在它们之间。
一、Hash 就是 Redis 里的“小字典”
在一个 Redis Key 下面,再挂一堆 field-value 对。很像编程语言里的字典或者对象。
HSET user:1001 name"张三"age25city"北京"这样,user:1001这个 Key 下,有三个字段:name、age、city。你可以单独改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 | 获取所有字段名 / 所有值 |
动手试试:
# 塞一个用户HSET user:1001 name"李四"age30balance1000# 单独取年龄HGET user:1001 age# "30"# 给余额加 200HINCRBY user:1001 balance200# 返回 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取你真正需要的字段。
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查看:
HSET small a1b2OBJECT 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 自动升级底层的存储方式。
127.0.0.1:6379> object encoding small"listpack"127.0.0.1:6379> object encoding big"hashtable"四、哈希字段过期(Redis 7.4+ 的神器)
在老版本里,整个 Hash 要么一起活,要么一起死。你不能让user:1001的temp_token字段 5 分钟失效,而name永久保留。
Redis 7.4引入了字段级过期,命令如下:
HEXPIRE key seconds[NX|XX|GT|LT]FIELDS numfields field[field...]参数说明
- key: 哈希键名称。
- seconds: TTL(以秒为单位)。
- FIELDS numfields field [field …]: 指定字段及其数量。
可选参数
- NX: 仅当字段没有过期时间时设置。
- XX: 仅当字段已有过期时间时设置。
- GT: 新的过期时间必须大于当前过期时间。
- LT: 新的过期时间必须小于当前过期时间。
示例:
# 为不存在的键设置过期时间HEXPIRE no-key20NX FIELDS2field1 field2(nil)# 创建一个哈希键并设置字段HSET examplekey field1"hello"field2"world"(integer)2# 为字段设置 10 秒的过期时间HEXPIRE examplekey10FIELDS3field1 field2 field31)(integer)1# field1 的过期时间成功设置2)(integer)1# field2 的过期时间成功设置3)(integer)-2# field3 不存在,返回 -2# 检查哈希键内容HGETALL mykey(empty array)画个图,更直观的了解下:
场景:用户登录后存session_id在 Hash 里,设置 30 分钟过期,同时保留用户基本资料。以前你要单独存一个 String Key,现在一个 Hash 搞定。
五、实战场景
场景1:用户信息 / 对象缓存
# 存用户资料HSET user:1001 name"王五"email"wang@example.com"points100# 增加积分HINCRBY user:1001 points50# 修改邮箱(只改一个字段)HSET user:1001 email"newwang@example.com"# 取出来展示HGETALL user:1001场景2:购物车(经典案例)
每个用户的购物车是一个 Hash,field是商品 ID,value是数量:
# 用户1001 添加 2个商品101,1个商品102HSET cart:1001 prod_1012HINCRBY cart:1001 prod_1021# 增加某种商品数量HINCRBY cart:1001 prod_1011# 现在变成3# 减少数量HINCRBY cart:1001 prod_101-1# 删除商品HDEL cart:1001 prod_102# 查看购物车所有商品HGETALL cart:1001购物车天然适合 Hash,商品数量可以单独增减,不需要序列化整个数组。
场景3:配置项 / 属性存储
比如存某个服务的开关配置:
HSET config:service_atimeout30retry3enable_ssl1HGET config:service_atimeout随时改单条配置,不用重写整个配置对象。
六、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。
📢 点关注,不迷路
如果你看到这儿了,欢迎点赞 + 关注,我们下期见~