哈希 Hash
本教程共 40 篇 · 第 7 篇 · 更新于 2026-08-02
7. 哈希 Hash
本节目标
- 理解 Hash 是「字段-值映射表」,适合表示对象
- 熟练使用
HSET/HGET/HMGET/HGETALL/HDEL/HEXISTS - 用
HINCRBY/HINCRBYFLOAT做字段级原子计数 - 掌握
HSCAN遍历大 Hash,避免HGETALL阻塞 - 了解字段级 TTL(Redis 7.4.0 起):
HEXPIRE/HTTL/HEXPIRETIME
如果说 String 适合存「一个整体值」,那 Hash 适合存「一个有很多属性的对象」——比如用户资料、商品信息、配置项。它把多个 field → value 收在同一个 key 下,可以单独读写某个字段,而不用整体取出再写回。
7-1 Hash 是什么
Redis 的 Hash 是一个 string 类型的 field 和 value 的映射表(field 和 value 都是字符串)。概念上等价于:
- Python 的
dict - Java 的
HashMap - 关系表里「某一行」
每个 Hash 最多可容纳 2³² − 1 个字段(约 42 亿),对业务来说基本无限。
7-2 基本读写
设置单个字段用 HSET(HSET 很早就支持一次设多个 field,无需再用单独的 HMSET;HMSET 仍可用但已非必须):
127.0.0.1:6379> HSET user:1001 name "张三" age 28 city "北京"
(integer) 3
127.0.0.1:6379> HGET user:1001 name
"张三"
127.0.0.1:6379> HMGET user:1001 name age
1) "张三"
2) "28"
HGET 取单个字段,HMGET 一次取多个字段。
取全部字段和值——注意风险:
127.0.0.1:6379> HGETALL user:1001
1) "name"
2) "张三"
3) "age"
4) "28"
5) "city"
6) "北京"
HGETALL 会一次性返回所有字段,当 Hash 很大(成千上万个字段)时会占用大量网络与内存,甚至阻塞。大 Hash 请用 HSCAN 分批遍历(见 7.6)。
7-3 字段管理
# 字段数量
127.0.0.1:6379> HLEN user:1001
(integer) 3
# 判断字段是否存在
127.0.0.1:6379> HEXISTS user:1001 age
(integer) 1
# 删除字段(可一次删多个)
127.0.0.1:6379> HDEL user:1001 city
(integer) 1
# 取所有字段名 / 所有值
127.0.0.1:6379> HKEYS user:1001
1) "name"
2) "age"
127.0.0.1:6379> HVALS user:1001
1) "张三"
2) "28"
此外,HSTRLEN key field 可取某字段值的字节长度;Redis 还提供 HRANDFIELD(自 6.0 起)随机返回一个字段(支持带值、带数量),非常适合「随机抽一名用户」「随机推荐」这类场景,且性能恒定、不会像 HKEYS 那样拉回全量。
7-4 字段级原子计数:HINCRBY
Hash 的字段值若为数字,可用 HINCRBY/HINCRBYFLOAT 原子增减——非常适合「对象里的某个数值属性」:
127.0.0.1:6379> HSET article:42 likes 0 views 0
(integer) 2
127.0.0.1:6379> HINCRBY article:42 likes 1
(integer) 1
127.0.0.1:6379> HINCRBY article:42 views 100
(integer) 100
127.0.0.1:6379> HGET article:42 likes
"1"
对比「把整个对象序列化进 String 再 INCR」:用 Hash,你只改 likes 这一个字段,其他字段不受影响,也无需整体读改写。
7-5 条件设置:HSETNX
HSETNX key field value 仅当该字段不存在时才设置,已存在则忽略:
127.0.0.1:6379> HSETNX user:1001 name "李四"
(integer) 0 # name 已存在,未修改
127.0.0.1:6379> HSETNX user:1001 nickname "小李"
(integer) 1 # 新字段,设置成功
7-6 大 Hash 遍历:HSCAN
HSCAN 基于游标增量遍历,不会阻塞服务器,是生产环境遍历大 Hash 的正确方式:
127.0.0.1:6379> HSCAN user:1001 0 COUNT 10
1) "0" # 下一次游标,0 表示遍历结束
2) 1) "name"
2) "张三"
3) "age"
4) "28"
可加 MATCH 按字段名模式过滤:
127.0.0.1:6379> HSCAN user:1001 0 MATCH attr:*
提示:不要用
KEYS遍历键空间,也不要对大 Hash 直接用HGETALL。两者都可能在数据量大时拖慢甚至阻塞 Redis。HSCAN/SSCAN/ZSCAN/SCAN才是安全选择。
7-7 字段级过期(Field TTL,Redis 7.4.0 起引入)
这是 Redis 7.4.0 引入的重要增强——可以为 Hash 里的单个字段设置过期时间,而不影响整个 key 或同 key 下的其他字段。相关命令:
HEXPIRE key seconds field [field ...]:给字段设过期(秒)HPEXPIRE:毫秒版本HTTL key field:查字段剩余 TTLHEXPIRETIME/HPERSIST:查过期时间戳 / 取消过期HGET key field+HTTL key field:分别取字段值与剩余 TTL(Redis 没有「一条命令同时返回值与剩余时间」的哈希命令)
127.0.0.1:6379> HSET session:u1 token "abc" tmpcode "123"
(integer) 2
# 给临时验证码字段设 60 秒过期,token 不过期
127.0.0.1:6379> HEXPIRE session:u1 60 tmpcode
1) (integer) 1
127.0.0.1:6379> HTTL session:u1 tmpcode
(integer) 57
提示:字段级 TTL 在 Redis 7.4.0 之前不存在。旧版本若想「某个属性过期」,只能把整个 key 设过期,或拆成独立 key。Redis 7.4.0 起原生支持后,像「会话里临时验证码单独过期」这类需求实现起来干净得多。
提示:注意区分两代新增能力——字段级过期(
HEXPIRE/HTTL/HEXPIRETIME/HPERSIST)来自 Redis 7.4.0;而 Redis 8.0 真正为 Hash 新增的是HGETEX/HSETEX/HGETDEL这组「带 TTL 地读取 / 写入 / 读取并删除字段」的命令,二者不要混淆。
7-8 什么时候该用 / 不该用 Hash
Hash 虽好,但并不是「对象存储」的唯一答案,也不是永远最优解:
- 适合:对象的字段数量中等(几十到几万),且经常需要单独读写某些字段或做字段级计数(如用户积分、文章点赞数)。此时 Hash 比「整体序列化进 String」省网络、省内存,还能原子更新。
- 不太适合——超宽 Hash:如果一个 Hash 有几十万甚至上百万字段,单个
HGETALL会一次性拉回海量数据,严重阻塞;即便用HSCAN遍历也很慢。这种「超宽映射」更适合拆成多个 key,或改用其他结构。 - 不太适合——需要嵌套 / 复杂查询:Hash 的 value 只能是字符串,无法表达嵌套对象、数组。如果你需要按任意字段做条件查询、范围查询,或存天然层级化的文档,Redis 8 内置的 JSON 类型(支持 JSONPath)通常更合适。
简单记:扁平的、字段级访问频繁的「对象」用 Hash;层级复杂、要结构化查询的文档用 JSON;只是要一个被整体读写的序列化 blob,用 String 也行。
7-9 实战:用 Hash 存用户对象
# 创建
127.0.0.1:6379> HSET user:1001 name "张三" age 28 city "北京" vip 1
(integer) 4
# 改其中一个字段
127.0.0.1:6379> HSET user:1001 city "上海"
(integer) 0
# 字段级计数(积分)
127.0.0.1:6379> HINCRBY user:1001 points 50
(integer) 50
# 读取
127.0.0.1:6379> HMGET user:1001 name city points
1) "张三"
2) "上海"
3) "50"
这比「把整个用户 JSON 塞进一个 String」更优:可以只改 city 而不动 name、points;可以对 points 做原子自增;网络传输字段也更省。
7-10 编码与内存(了解即可)
为了省内存,小 Hash 在 Redis 内部用紧凑的 listpack(Redis 7 前叫 ziplist)编码;当字段数或字段值超过阈值(hash-max-listpack-entries 512、hash-max-listpack-value 64)时,自动转成真正的哈希表。这是 Redis 自动完成的,你无需干预,但知道它存在有助于理解「为什么小对象特别省内存」。
7-11 常见误区
- 误区:HGETALL 万能。 大 Hash 上
HGETALL会一次性返回全部字段,可能阻塞;大对象用HSCAN。 - 误区:Hash 里的字段值是数字就能 INCR。 要用
HINCRBY,普通的INCR是作用在 String key 上的,不能作用于 Hash 字段。 - 误区:整个 key 过期 = 字段各自过期。 过期是按 key 维度的;字段级 TTL 是 Redis 7.4.0 才引入的能力。
- 误区:HMSET 才有、HSET 一次只能一个字段。 在 Redis 8,
HSET已支持一次设多个 field,HMSET仍可用但非必须。
7-12 小结
- Hash 是 field→value 映射,适合存「对象」,支持字段级读写与原子计数。
- 常用:
HSET/HGET/HMGET/HGETALL/HDEL/HEXISTS/HLEN/HKEYS/HVALS。 - 计数:
HINCRBY/HINCRBYFLOAT;条件写:HSETNX。 - 大对象遍历用
HSCAN,别用HGETALL。 - 字段级 TTL(
HEXPIRE/HTTL/HEXPIRETIME,Redis 7.4.0 起),可为单个字段设过期。 - 对比 String:对象用 Hash,整体值/计数用 String。
提示:版本基线说明。本教程正文统一以 Redis 8.x(最新稳定版) 表述。需要留意:官方 redis.io 下载页目前把
8.8标注为 “Latest stable”,而 GitHub 上 redis/redis 的最新发布标签为8.10.0;二者同属 8.x 大版本,命令与数据类型差异极小,本教程以8.10.0为准。
下一章我们看 List——Redis 的列表类型,天然适合做队列、栈和「最新 N 条」这类有序序列。