一、前言:为什么用 Hash 存储对象?
在 Redis 中,Hash(哈希) 是一种非常高效的键值对集合类型,特别适合存储对象。
例如:
- 用户信息:
user:1001 → {name: "Alice", age: 25, email: "a@example.com"} - 商品详情:
product:2001 → {title: "手机", price: 2999, stock: 100}
相比将每个字段拆成独立 key(如 user:1001:name、user:1001:age),Hash 具有:
✅ 内存更省 (Redis 对小 Hash 有特殊编码优化)
✅ 操作原子性 (单个 Hash 内部操作是原子的)
✅ 网络开销更低(一次命令获取多个字段)
本文将系统讲解 Redis Hash 的所有核心命令 ,并附带实战示例与最佳实践。
二、Hash 核心命令速查表
| 命令 | 作用 | 时间复杂度 |
|---|---|---|
HSET key field value [field value ...] |
设置一个或多个字段 | O(1) 每个字段 |
HGET key field |
获取指定字段的值 | O(1) |
HMSET key field value [field value ...] |
已废弃 ,用 HSET 替代 |
--- |
HMGET key field [field ...] |
获取多个字段的值 | O(N),N=字段数 |
HGETALL key |
获取所有字段和值 | O(N),N=字段数 |
HDEL key field [field ...] |
删除一个或多个字段 | O(N),N=字段数 |
HEXISTS key field |
判断字段是否存在 | O(1) |
HLEN key |
获取 Hash 字段数量 | O(1) |
HKEYS key |
获取所有字段名 | O(N) |
HVALS key |
获取所有字段值 | O(N) |
HINCRBY key field increment |
对整数值字段加减 | O(1) |
HINCRBYFLOAT key field increment |
对浮点值字段加减 | O(1) |
HSTRLEN key field |
获取字段值的字符串长度 | O(1) |
💡 注意 :从 Redis 4.0 起,
HMSET被标记为 deprecated,统一使用HSET。
三、常用命令详解与示例
3.1 写入数据:HSET
bash
# 设置单个字段
127.0.0.1:6379> HSET user:1001 name "Alice"
(integer) 1 # 1=新增,0=更新
# 设置多个字段(推荐!)
127.0.0.1:6379> HSET user:1001 age 25 email "alice@example.com" city "Beijing"
(integer) 3 # 新增了 3 个字段
✅ 优势:一次命令完成多字段写入,减少网络往返。
3.2 读取数据:HGET / HMGET / HGETALL
bash
# 获取单个字段
127.0.0.1:6379> HGET user:1001 name
"Alice"
# 获取多个字段(按需加载,避免全量)
127.0.0.1:6379> HMGET user:1001 name age city
1) "Alice"
2) "25"
3) "Beijing"
# 获取所有字段(慎用!大数据量会阻塞)
127.0.0.1:6379> HGETALL user:1001
1) "name"
2) "Alice"
3) "age"
4) "25"
5) "email"
6) "alice@example.com"
7) "city"
8) "Beijing"
⚠️ 生产建议:
- 避免在大 Hash 上使用
HGETALL- 优先使用
HMGET按需获取字段
3.3 删除与判断:HDEL / HEXISTS
bash
# 删除字段
127.0.0.1:6379> HDEL user:1001 email city
(integer) 2
# 判断字段是否存在
127.0.0.1:6379> HEXISTS user:1001 email
(integer) 0 # 不存在
127.0.0.1:6379> HEXISTS user:1001 name
(integer) 1 # 存在
3.4 计数与遍历:HLEN / HKEYS / HVALS
bash
# 字段数量
127.0.0.1:6379> HLEN user:1001
(integer) 2
# 获取所有字段名(调试用)
127.0.0.1:6379> HKEYS user:1001
1) "name"
2) "age"
# 获取所有值
127.0.0.1:6379> HVALS user:1001
1) "Alice"
2) "25"
🔍 注意 :
HKEYS/HVALS/HGETALL在字段数 > 1000 时可能阻塞主线程!
3.5 数值自增:HINCRBY / HINCRBYFLOAT
bash
# 整数自增(常用于计数器)
127.0.0.1:6379> HSET product:2001 view_count 100
127.0.0.1:6379> HINCRBY product:2001 view_count 1
(integer) 101
# 浮点自增(慎用,精度问题)
127.0.0.1:6379> HINCRBYFLOAT product:2001 avg_score 0.5
"0.5"
✅ 典型场景:商品浏览量、用户积分、评分统计。
四、Hash 的内部编码优化
Redis 对小 Hash 使用 ziplist(压缩列表) 编码,节省内存:
# redis.conf 默认配置
hash-max-ziplist-entries 512 # 字段数 ≤ 512
hash-max-ziplist-value 64 # 每个值长度 ≤ 64 字节
当超过阈值,自动转为 hashtable 编码。
💡 建议:
- 小对象(如用户 profile)非常适合 Hash
- 大对象(如文章内容)建议用 String 或单独 key
五、实战应用场景
场景 1:用户信息缓存
java
// Java (Lettuce)
redis.hset("user:" + userId,
Map.of("name", "Alice", "age", "25", "email", "a@example.com"));
String name = redis.hget("user:" + userId, "name");
List<String> fields = redis.hmget("user:" + userId, "name", "age");
场景 2:商品库存与价格
python
# Python (redis-py)
r.hset("product:1001", mapping={
"title": "iPhone 15",
"price": "5999",
"stock": "50"
})
# 扣减库存(Lua 脚本保证原子性)
script = """
if tonumber(redis.call('HGET', KEYS[1], 'stock')) >= tonumber(ARGV[1]) then
redis.call('HINCRBY', KEYS[1], 'stock', -ARGV[1])
return 1
else
return 0
end
"""
r.eval(script, 1, "product:1001", 1)
场景 3:会话状态管理
bash
HSET session:abc123 user_id 1001 login_time 1700000000 ip "192.168.1.1"
HEXPIRE session:abc123 1800 # Redis 7.0+ 支持 Hash 级 TTL
📌 注意 :Redis 7.0+ 支持
HEXPIRE,但早期版本需对整个 key 设 TTL。
六、常见误区与最佳实践
❌ 误区 1:用 Hash 存储超大对象
- 问题 :
HGETALL阻塞主线程 - 建议:大对象拆分为多个 key,或使用 String + JSON
❌ 误区 2:频繁使用 HGETALL
- 问题:网络带宽浪费 + 性能下降
- 建议 :明确需要哪些字段,用
HMGET
✅ 最佳实践
- 字段命名规范 :如
user_id、create_time - 避免动态字段爆炸 :如
HSET log:20250101 ip_192.168.1.1 1(IP 无限) - 监控 Hash 大小 :通过
HLEN+ 告警防止膨胀
七、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!