首页 / Redis 入门教程 / 哈希 Hash

Redis 入门教程

哈希 Hash

本教程共 40 篇 · 第 7 篇 · 更新于 2026-08-02

redishash哈希对象存储字段过期

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 基本读写

设置单个字段用 HSETHSET 很早就支持一次设多个 field,无需再用单独的 HMSETHMSET 仍可用但已非必须):

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:查字段剩余 TTL
  • HEXPIRETIME / 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 而不动 namepoints;可以对 points 做原子自增;网络传输字段也更省。

7-10 编码与内存(了解即可)

为了省内存,小 Hash 在 Redis 内部用紧凑的 listpack(Redis 7 前叫 ziplist)编码;当字段数或字段值超过阈值(hash-max-listpack-entries 512hash-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 条」这类有序序列。