一、前言:为什么用 Hash 存储对象更高效?
在 Redis 中,如果你需要存储一个用户、商品或订单等结构化对象,你会怎么选?
- ❌ 方案 A:用
SET user:1001 '{"name":"张三","age":28}'(JSON 字符串) - ✅ 方案 B:用
HSET user:1001 name "张三" age 28(Hash 结构)
方案 B 更优 !因为 Hash 支持字段级操作,无需反序列化整个对象,内存占用更低,更新更高效。
本文将带你:
✅ 全面掌握 Hash 类型的核心命令
✅ 理解其底层编码机制
✅ 结合真实场景(用户资料、商品属性)
✅ 避开常见使用误区
二、Hash 类型基本特性
- 结构 :一个 Key 对应多个 field-value 对(类似 Map)
- 适用场景:存储对象、配置项、属性集合
- 底层编码 :
- ziplist:当字段少、值小时(默认 ≤ 512 个 field,每个 value ≤ 64 字节)
- hashtable:超出阈值后自动转为哈希表
- 内存效率高:相比 String 存 JSON,可节省 20%~50% 内存
三、核心命令详解(附实战示例)
1. HSET key field value [field value ...]
设置一个或多个字段的值(覆盖写入)。
bash
# 设置单个字段
HSET user:1001 name "张三"
# 批量设置(推荐!减少网络往返)
HSET user:1001 age 28 city "北京" gender "男"
⚡ 返回值:新增字段数量(若字段已存在,不计入)
2. HGET key field
获取指定字段的值。
bash
HGET user:1001 name
# 返回 "张三"
HGET user:1001 non_existent
# 返回 (nil)
3. HMGET key field [field ...]
批量获取多个字段(强烈推荐替代多次 HGET)。
bash
HMGET user:1001 name age city
# 返回 1) "张三" 2) "28" 3) "北京"
✅ 性能优势:1 次
HMGET≈ 3 次HGET的 1/3 延迟
4. HGETALL key
获取所有字段和值。
bash
HGETALL user:1001
# 返回 1) "name" 2) "张三" 3) "age" 4) "28" ...
⚠️ 慎用 :当 Hash 包含成千上万个字段时,会阻塞 Redis!
✅ 替代方案:用
HSCAN分批遍历
5. HDEL key field [field ...]
删除一个或多个字段。
bash
# 删除用户城市信息
HDEL user:1001 city
# 删除多个字段
HDEL user:1001 gender hobby
💡 不会删除整个 Key,除非所有字段都被删光
6. HEXISTS key field
判断字段是否存在。
bash
HEXISTS user:1001 email
# 返回 1(存在)或 0(不存在)
✅ 典型用途:避免对不存在的字段执行操作
7. HINCRBY / HINCRBYFLOAT
对数值型字段进行原子自增。
bash
# 登录次数 +1
HINCRBY user:1001 login_count 1
# 余额增加 99.9
HINCRBYFLOAT user:1001 balance 99.9
🔒 原子性保证:并发安全,无需额外锁
8. HKEYS / HVALS
HKEYS key:获取所有字段名HVALS key:获取所有字段值
⚠️ 同
HGETALL,大数据量时慎用!
四、Hash 的典型应用场景
场景 1:用户资料存储
bash
HSET user:profile:1001 \
name "李四" \
avatar "https://xxx.jpg" \
bio "热爱编程" \
followers 1200 \
created_at "2023-01-01"
✅ 优势:
- 更新头像只需
HSET user:profile:1001 avatar "new.jpg"- 无需读取整个对象再写回
场景 2:商品属性管理
bash
HSET product:8888 \
title "iPhone 16 Pro" \
price 9999 \
stock 500 \
color "深空黑" \
category "手机"
💡 电商系统中,商品 SKU 信息天然适合用 Hash
场景 3:配置中心
bash
HSET app:config:homepage \
banner_url "https://..." \
ad_enabled "true" \
version "2.1.0"
✅ 动态更新某个配置项,不影响其他字段
五、Hash vs String(存 JSON)对比
| 维度 | Hash | String(JSON) |
|---|---|---|
| 内存占用 | 更低(尤其小对象) | 较高(含 JSON 语法字符) |
| 更新粒度 | 字段级(高效) | 全量覆盖(低效) |
| 读取性能 | HMGET 批量快 |
需反序列化整个字符串 |
| 适用对象大小 | 中小型对象(< 1KB) | 任意大小(但大对象不推荐) |
📌 建议:
- 对象字段 ≤ 100 个 → 优先用 Hash
- 对象嵌套复杂或需跨语言共享 → 可考虑 String + JSON
六、常见误区与最佳实践
❌ 误区 1:用 Hash 存超大对象(如 10MB 的日志)
问题 :导致 Redis 主线程阻塞,影响其他请求
建议:大对象拆分或存数据库,Redis 只存 ID
❌ 误区 2:频繁调用 HGETALL 获取全量数据
风险 :O(N) 操作,大数据量卡顿
替代 :按需HMGET,或用HSCAN分页
✅ 最佳实践:
- Key 命名规范 :
业务:实体:id(如user:profile:1001) - 避免 Big Hash:单个 Hash 字段数 ≤ 5000
- 批量操作优先 :
HSET/HMGET一次传多字段 - 数值字段用
HINCRBY:避免先HGET再计算再HSET
七、底层编码切换阈值(拓展知识)
Redis 默认在以下条件同时满足 时使用 ziplist 编码:
- 字段数量 ≤
hash-max-ziplist-entries(默认 512) - 每个 field/value 长度 ≤
hash-max-ziplist-value(默认 64 字节)
可通过 redis.conf 调整:
hash-max-ziplist-entries 1024
hash-max-ziplist-value 128
💡 调大可节省更多内存,但可能增加 CPU 开销(压缩/解压 ziplist)
八、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!