跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
编程语言算法

Redis 哈希(Hash)深度解析:Field-Value 层级、原子性与内部编码

综述由AI生成深入解析 Redis 哈希(Hash)结构,对比了 Field-Value 与 Key-Value 的层级嵌套关系及原子性差异。介绍了 HSET、HGET 等核心命令的使用场景与性能风险,并详细阐述了 ziplist、listpack 及 hashtable 三种内部编码的触发条件、特点及转换规则,为优化 Redis 内存使用与性能提供实践指导。

赛博行者发布于 2026/3/26更新于 2026/5/1024 浏览
Redis 哈希(Hash)深度解析:Field-Value 层级、原子性与内部编码

文章配图

引言

在 Redis 中,哈希结构具有独特的深层意义,不同于常规的 Key-Value 结构,这里引入了 Field 的概念。

Redis 自身已经是键值对结构,通过哈希方式组织。当 key 这一层组织完成后,value 的其中一种类型还可以再是哈希。

形如 key="key",value={{field1, value1}, …, {fieldN, valueN}},Redis 键值对和哈希类型的关系如下所示。

文章配图

Field-Value 与 Key-Value 的深层次解析

在 Redis 中,Field-Value 和 Key-Value 是层级嵌套关系。其中 key 是全局唯一标识符,指向一个哈希(Hash)结构;而 field 是该哈希内部的字段名,与对应的 value 组成键值对,存储在哈希中。

1. 层级关系:全局 Key → 哈希结构 → 多个 Field-Value
  • 全局 Key:Redis 中的每个数据对象(如字符串、列表、哈希等)都通过唯一的 key 标识。例如,user:1001 可能是一个全局 Key,指向某个用户的数据。
  • 哈希结构:当全局 Key 对应的数据类型是哈希(Hash)时,其值是一个字段 - 值对的集合。例如,user:1001 可能存储为一个哈希,包含用户的多个属性。
  • Field-Value:哈希内部的每个属性由 field(字段名)和 value(字段值)组成。例如,name: "Alice"、age: 30 等。
2. Field 的定义与作用
  • Field 的本质:Field 是哈希内部的键(局部键),用于区分哈希中的不同属性。它类似于编程语言中的对象属性名或数据库表中的列名。
  • 为什么需要 Field:
    • 结构化存储:Field 允许将多个相关属性组织在一个哈希中,避免为每个属性创建单独的全局 Key(如 user:1001:name、user:1001:age),从而减少 Key 数量,提升管理效率。
    • 原子性操作:Redis 支持对哈希的单个 Field 进行原子操作(如 HINCRBY 递增数值),无需锁定整个哈希或全局 Key,适合高并发场景。
    • 内存优化:哈希在存储多个小字段时比单独的字符串更节省内存,尤其是当字段数量较多时。
3. 示例说明

假设需要存储用户信息:

  • 缺点(无 Field):Key 数量多,管理复杂;修改年龄需操作独立 Key,无法保证原子性。
  • 优点(有 Field):Key 数量少;可通过 HSET user:1001 age 31 原子性更新年龄;内存占用更低。

有 Field 的设计(使用哈希):

Key: user:1001 Field: name, Value: "Alice"
Field: age, Value: 30

无 Field 的设计:

Key: user:1001:name, Value: "Alice"
Key: user:1001:age, Value: 30
4. 核心区别总结
特性Key-Value(全局)Field-Value(哈希内部)
作用域整个 Redis 数据库单个哈希结构内部
唯一性全局唯一在哈希内唯一
典型操作GET key、SET key valueHGET key field、HSET key field value
设计目的标识数据对象描述对象属性
5. 适用场景
  • 使用 Field-Value(哈希):
    • 存储对象(如用户、商品、订单)。
    • 需要原子性更新部分属性。
    • 字段数量较多且需节省内存。
  • 使用 Key-Value(字符串):
    • 存储简单数据(如配置项、计数器)。
    • 需要独立生命周期或全局唯一标识的场景。

为什么 Field-Value 是原子性的,而 Key-Value 不是原子性的

在 Redis 中,Field-Value(哈希内部的字段值)的原子性操作是相对于哈希结构而言的,而Key-Value(全局键值对)的原子性操作是针对整个键的。两者的原子性范围不同,导致它们的特性有所差异。

1. 原子性的定义
  • 原子性:指一个操作要么完全执行,要么完全不执行,中间不会因其他操作或故障而中断。在 Redis 中,原子性通常由单命令保证(如 SET、HSET、INCR 等)。
2. 为什么 Field-Value 是原子性的?
(1)哈希结构的原子性操作

Redis 为哈希(Hash)提供了针对单个字段的原子操作命令,例如:

  • HSET key field value:设置字段值。
  • HGET key field:获取字段值。
  • HINCRBY key field increment:原子性递增字段的数值。
  • HDEL key field:删除字段。

这些命令直接操作哈希中的某个字段,Redis 保证它们的执行是原子的。例如:

HINCRBY user:1001 age 1 # 原子性将 age 字段的值 +1

即使多个客户端同时执行此命令,Redis 也会通过内部锁机制确保最终结果正确(如 age 从 30 变为 31,不会出现中间状态)。

(2)原子性范围
  • 哈希的原子性仅限于单个字段:如果需要同时修改多个字段(如 name 和 age),必须使用 HMSET 或事务(MULTI/EXEC),此时原子性扩展到整个命令或事务。
  • 哈希本身不是全局原子性的:如果其他客户端修改了哈希的其他字段(如 email),不会影响当前字段的操作。
3. 为什么 Key-Value(字符串)的原子性表现不同?
(1)字符串的原子性操作

Redis 对字符串(String)也提供原子操作,例如:

  • SET key value:设置键值。
  • GET key:获取键值。
  • INCR key:原子性递增数值。
  • DECR key:原子性递减数值。

这些命令直接操作整个键,原子性范围是全局的。例如:

INCR counter # 原子性将 counter 的值 +1
(2)关键区别:操作范围
  • 字符串的原子性是全局的:操作 counter 时,其他客户端无法同时修改它,直到当前操作完成。
  • 哈希的原子性是局部的:操作 user:1001:age 时,其他客户端可以同时修改 user:1001:name,两者互不干扰。
(3)误解澄清:Key-Value 本身也是原子性的
  • 字符串的 SET/GET 等操作本身就是原子性的,但问题可能源于以下场景:
    • 复合操作非原子性:如果需要先 GET 再 SET(如 value = GET key; SET key value+1),这不是原子性的,需改用 INCR。
    • 与哈希的对比:哈希的原子性是针对字段的,而字符串的原子性是针对键的。如果比较的是'修改哈希的多个字段' vs '修改字符串的单个键',前者需要事务,后者天然原子。

命令篇

注意 H 系列的命令必须要保证 key 对应的 value 是哈希类型的!!!

HSET

设置 hash 中指定的字段(field)的值(value)。

HSET key field value [field value ...]

时间复杂度:插入一组 field 为 O(1),插入 N 组 field 为 O(N)。

返回值:添加的字段的个数。

示例:

redis> HSET myhash field1 "Hello" (integer) 1
redis> HGET myhash field1 "Hello"
HGET

获取 hash 中指定字段的值。

HGET key field

时间复杂度 O(1)。

返回值:字段对应的值或者 nil。

示例:

redis> HSET myhash field1 "foo" (integer) 1
redis> HGET myhash field1 "foo"
redis> HGET myhash field2 (nil)
HEXIST

判断 hash 中是否有指定的字段。

HEXISTS key field

时间复杂度:O(1)。

返回值:1 表示存在,0 表示不存在。

示例:

redis> HSET myhash field1 "foo" (integer) 1
redis> HEXISTS myhash field1 (integer) 1
redis> HEXISTS myhash field2 (integer) 0
HDEL

删除哈希中指定的字段。

HDEL key field [field ...]

时间复杂度:删除一个元素为 O(1),删除 N 个元素为 O(N)。

返回值:本次操作删除的字段个数。

示例:

redis> HSET myhash field1 "foo" (integer) 1
redis> HDEL myhash field1 (integer) 1
redis> HDEL myhash field2 (integer) 0
HKEYS

获取 hash 中所有的字段。

HKEYS key

时间复杂度:O(N),N 为 field 的个数。

返回值:字段列表。

示例:

redis> HSET myhash field1 "Hello" (integer) 1
redis> HSET myhash field2 "World" (integer) 1
redis> HKEYS myhash
1) "field1"
2) "field2"

注意:这个操作也是存在一定的风险的!!!类似于之前介绍过的 keys 命令。 主要是咱们也不知道某个 hash 中是否会存在大量的 field~

HVALS

获取 hash 中的所有的值。

语法:

HVALS key

时间复杂度:O(N),N 为 field 的个数。

返回值:所有的值。

示例:

redis> HSET myhash field1 "Hello" (integer) 1
redis> HSET myhash field2 "World" (integer) 1
redis> HVALS myhash
1) "Hello"
2) "World"

注意如果哈希非常大,这个操作就可能导致 redis 服务器被阻塞。

HGETALL

获取 hash 中所有字段以及对应的值。

HGETALL key

时间复杂度:O(N),N 为 field 的个数。

返回值:字段和对应的值。

示例:

redis> HSET myhash field1 "Hello" (integer) 1
redis> HSET myhash field2 "World" (integer) 1
redis> HGETALL myhash
1) "field1"
2) "Hello"
3) "field2"
4) "World"
HMGET

一次获取 hash 中多个字段的值。

HMGET key field [field ...]

时间复杂度:只查询一个元素为 O(1),查询多个元素为 O(N),N 为查询元素个数。

返回值:字段对应的值或者 nil。

文章配图

redis> HSET myhash field1 "Hello" (integer) 1
redis> HSET myhash field2 "World" (integer) 1
redis> HMGET myhash field1 field2 nofield
1) "Hello"
2) "World"
3) (nil)
小总结

上述 hkeys、hvals、hgetall 都是存在一定风险的(一条命令,就能完成所有的遍历操作)。hash 的元素个数太多,执行的耗时会比较长,从而阻塞 Redis。

在使用 HGETALL 时,如果哈希元素个数比较多,会存在阻塞 Redis 的可能。如果开发人员只需要获取部分 field,可以使用 HMGET,如果一定要获取全部 field,可以尝试使用 HSCAN 命令,该命令采用渐进式遍历哈希类型~

HSCAN 的思想

敲一次命令,遍历一小部分, 再敲一次,再遍历一小部分 化整为零 连续执行多次,就可以完成整个的遍历过程了

哈希内部编码

在 Redis 中,哈希(Hash)类型的内部编码主要有 ziplist(压缩列表) 和 hashtable(哈希表) 两种,在 Redis 7.0 及以后版本中,ziplist 被 listpack(紧凑列表)替代,但核心设计思想类似。以下是具体说明:

1. ziplist(压缩列表)
  • 适用场景:当哈希的元素数量较少且单个元素较小时,Redis 会使用 ziplist 作为内部编码。
  • 触发条件:
    • 元素数量小于 hash-max-ziplist-entries(默认 512 个)。
    • 所有元素的值大小均小于 hash-max-ziplist-value(默认 64 字节)。
  • 特点:
    • 内存紧凑:ziplist 通过连续内存存储多个字段和值,减少指针开销,节省内存。
    • 顺序存储:字段和值按插入顺序连续存储,适合小规模数据。
    • 性能权衡:当元素数量或大小超过阈值时,读写效率会下降(需扩容或遍历)。

示例:

127.0.0.1:6379> HMSET user:1 name "Alice" age 30 OK
127.0.0.1:6379> OBJECT ENCODING user:1 "ziplist"
2. hashtable(哈希表)
  • 适用场景:当哈希的元素数量较多或单个元素较大时,Redis 会切换到 hashtable 作为内部编码。
  • 触发条件:
    • 元素数量超过 hash-max-ziplist-entries。
    • 任意元素的值大小超过 hash-max-ziplist-value。
  • 特点:
    • O(1) 时间复杂度:哈希表通过哈希函数直接定位字段,读写效率高。
    • 内存开销较大:需维护哈希表结构(如数组、链表或红黑树),指针开销较高。
    • 动态扩容:当负载因子超过阈值时,哈希表会自动扩容(rehash)。

示例:

127.0.0.1:6379> HSET user:2 info "This is a long string that exceeds 64 bytes..." OK
127.0.0.1:6379> OBJECT ENCODING user:2 "hashtable"
3. listpack(紧凑列表,Redis 7.0+)
  • 背景:ziplist 在极端情况下(如连续更新大元素)可能引发'连锁更新'问题,导致性能抖动。Redis 7.0 引入 listpack 替代 ziplist,优化内存布局和更新效率。
  • 特点:
    • 内存更高效:通过改进节点编码(如前驱节点长度存储优化),减少内存碎片。
    • 避免连锁更新:节点长度变更时,仅影响相邻节点,而非整个列表。
    • 兼容性:与 ziplist 的 API 兼容,无需修改上层命令。
内部编码转换规则
  • 单向性:编码转换仅从小内存编码(如 ziplist/listpack)向大内存编码(如 hashtable)进行,不可逆。
  • 动态调整:Redis 根据配置参数和实际数据量自动选择编码,无需手动干预。
配置参数优化

可通过修改 Redis 配置文件(redis.conf)或运行时使用 CONFIG SET 命令调整哈希的内部编码行为:

# 调整 ziplist/listpack 的最大元素数量(默认 512)
hash-max-ziplist-entries 1024
# 调整 ziplist/listpack 中单个元素的最大值大小(默认 64 字节)
hash-max-ziplist-value 128
应用场景建议
  • 使用 ziplist/listpack:存储字段数量少、值较小的哈希(如用户基本信息、配置项)。
  • 使用 hashtable:存储字段数量多或值较大的哈希(如商品详情、日志数据)。

缓存方式对比

原生字符串类型

使用字符串类型,每个属性一个键。

set user:1:name James
set user:1:age 23
set user:1:city Beijing

优点:实现简单,针对个别属性变更也很灵活。

缺点:占用过多的键,内存占用量较大,同时用户信息在 Redis 中比较分散,缺少内聚性,所以这种方案基本没有实用性。

序列化字符串类型

例如 JSON 格式。

set user:1 经过序列化后的用户对象字符串

优点:针对总是以整体作为操作的信息比较合适,编程也简单。同时,如果序列化方案选择合适,内存的使用效率很高。

缺点:本身序列化和反序列化需要一定开销,同时如果总是操作个别属性则非常不灵活。

哈希类型

优点:简单、直观、灵活。尤其是针对信息的局部变更或者获取操作。

缺点:需要控制哈希在 ziplist 和 hashtable 两种内部编码的转换,可能会造成内存的较大消耗。

哈希的两种编码为什么需要自动转换?

Redis 根据哈希的 字段数量 和 字段值大小 动态选择内部编码,目的是:

(1)避免极端情况下的性能或内存问题
  • 如果始终用 ziplist: 当哈希字段数量或值过大时,ziplist 的插入/删除操作会变得非常低效(需频繁移动内存),甚至可能引发内存碎片或 OOM(内存不足)。
  • 如果始终用 hashtable: 对小数据使用 hashtable 会浪费内存(指针开销占比高),且哈希表的初始化成本高于 ziplist。
(2)适应不同业务场景的需求
  • 读多写少的小数据: ziplist 的内存优势更明显,适合配置类、缓存类数据。
  • 高并发写或大数据: hashtable 的 O(1) 操作效率更关键,适合热点数据或频繁更新的场景。

目录

  1. 引言
  2. Field-Value 与 Key-Value 的深层次解析
  3. 1. 层级关系:全局 Key → 哈希结构 → 多个 Field-Value
  4. 2. Field 的定义与作用
  5. 3. 示例说明
  6. 4. 核心区别总结
  7. 5. 适用场景
  8. 为什么 Field-Value 是原子性的,而 Key-Value 不是原子性的
  9. 1. 原子性的定义
  10. 2. 为什么 Field-Value 是原子性的?
  11. (1)哈希结构的原子性操作
  12. (2)原子性范围
  13. 3. 为什么 Key-Value(字符串)的原子性表现不同?
  14. (1)字符串的原子性操作
  15. (2)关键区别:操作范围
  16. (3)误解澄清:Key-Value 本身也是原子性的
  17. 命令篇
  18. HSET
  19. HGET
  20. HEXIST
  21. HDEL
  22. HKEYS
  23. HVALS
  24. HGETALL
  25. HMGET
  26. 小总结
  27. HSCAN 的思想
  28. 哈希内部编码
  29. 1. ziplist(压缩列表)
  30. 2. hashtable(哈希表)
  31. 3. listpack(紧凑列表,Redis 7.0+)
  32. 内部编码转换规则
  33. 配置参数优化
  34. 调整 ziplist/listpack 的最大元素数量(默认 512)
  35. 调整 ziplist/listpack 中单个元素的最大值大小(默认 64 字节)
  36. 应用场景建议
  37. 缓存方式对比
  38. 原生字符串类型
  39. 序列化字符串类型
  40. 哈希类型
  41. 哈希的两种编码为什么需要自动转换?
  42. (1)避免极端情况下的性能或内存问题
  43. (2)适应不同业务场景的需求
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • AI 飞速发展下,我们的职业发展路径有何变化?
  • 文心一言 4.5 开源模型本地化部署与测试分析
  • MC.JS WEBMC 移动端在在线教育中的应用案例
  • AI 产品经理核心能力与大模型学习路径指南
  • 队列的数组模拟实现与 STL queue 容器实战
  • 宇树 G1 人形机器人强化学习训练配置与奖励函数解析
  • Llama Factory 微调加速指南:GPU 并行训练部署
  • HeyGem 数字人视频生成:语音驱动唇形同步技术解析
  • Linux 项目环境搭建:libwebkit2gtk-4.1-0 安装详解
  • MyBatisPlus 后端与 Thymeleaf 前端全栈分页整合方案
  • Android Studio 安装及核心组件配置指南(SDK、JDK、Gradle)
  • Copilot Pro 使用指南:模型选择与配额管理策略
  • Android Studio 安装及组件配置:SDK、JDK 与 Gradle
  • 利用 LangChain 实现大语言模型与数据库的交互对话
  • Python 实现中秋月相计算、月饼切分与可视化
  • 一切皆是映射:深入理解 DQN 的稳定性与收敛性
  • LFM2.5-1.2B-Thinking 模型:打造个人 AI 写作助手
  • 在线学生成绩综合统计分析系统的设计与实现
  • 红黑树的平衡规则与旋转调整技巧
  • Spring Data 实体属性命名避坑:MySQL 保留关键字处理

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online