redis不支持真正嵌套结构,hset存储的json字段仅为字符串,修改需get-解析-修改-set,无原子性;推荐用redis.json模块或拆分为多个key(如hash+list+set)实现可靠操作。

Redis原生不支持真正的嵌套结构,HSET存的只是扁平字段
你用 HSET user:1001 name "Alice" address "{\"city\":\"Beijing\"}",那个 address 字段在 Redis 里就是一个普通字符串,不是可操作的子 Hash。后续想改 city,只能 HGET user:1001 address → 解析 JSON → 修改 → HSET 回去,中间没原子性,高并发时容易覆盖。
常见错误现象:HGET user:1001 address.city 报错或返回 nil;业务代码里反复 GET + json.loads + modify + SET,日志里频繁出现数据不一致。
- Redis 的 Hash 是严格一维的:每个 field 对应一个 string value,不识别内部结构
- 所谓“嵌套”,实际是应用层约定(比如把 JSON 当 field 值),Redis 本身不解析、不索引、不校验
- 如果真需要字段级原子操作(如
address.city自增、条件更新),必须靠外部逻辑或 Lua 脚本兜底
EVAL 脚本能安全读写 JSON 字段,但得自己处理解析和序列化
Lua 脚本在 Redis 服务端原子执行,适合封装对 JSON 字符串字段的读取、修改、合并等操作。前提是你的 Redis 已启用 redis.json 模块(Redis Stack)或使用纯 Lua 实现简易 JSON 处理(不推荐用于复杂结构)。
如果你用的是标准 Redis(无 RedisJSON),最简方案是用 Lua 手动拼接/拆解 JSON 字符串:
redis.call('HSET', KEYS[1], 'address', '{"city":"' .. ARGV[1] .. '","street":"' .. ARGV[2] .. '"}')
但这种方式脆弱:字段名硬编码、无转义、无法增量更新。真正可用的做法是引入 redis.json 模块后使用 JSON.SET/JSON.GET 命令,再用 EVAL 封装逻辑:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
-
JSON.SET user:1001 $.address.city "Shanghai"—— 直接定位并更新嵌套字段 -
JSON.GET user:1001 $.address—— 只取子结构,不拉整个 Hash - 脚本中可组合
JSON.GET+ 条件判断 +JSON.SET,实现“读-改-写”原子性 - 注意:模块需提前加载,命令不存在会报
(error) ERR unknown command
Hash + List/Set 组合比“伪嵌套”更可靠,尤其涉及集合操作
当业务中有“用户-订单-商品”这类一对多关系,别把订单列表塞进 Hash 的某个 JSON 字段里。正确做法是拆成多个 key,用命名空间关联:
- 主信息走 Hash:
HSET user:1001 name "Alice" email "a@b.com" - 订单 ID 列表用 List:
LPUSH user:1001:orders "order:778" "order:779" - 每个订单单独存 Hash:
HSET order:778 status "paid" total 299.9 - 商品 ID 集合用 Set:
SADD order:778:items "item:101" "item:102"
这样做的好处:
- 订单追加、查询最新 N 条、按时间范围分页,List 原生支持
LRANGE和LTRIM - 查某订单是否含某商品,
SISMEMBER order:778:items item:101是 O(1) - 删订单时,
DEL order:778 order:778:items清理干净,不会残留脏 JSON 字符串 - 所有操作都是原子命令,无需 Lua 就能保证一致性
用 Lua 做嵌套更新前,先确认是不是过度设计
很多团队写 Lua 脚本只为了“看起来像操作对象”,结果脚本越写越长,调试困难,升级 Redis 后还可能因 Lua 版本差异出错。其实多数场景下,应该优先问自己:
- 这个嵌套字段是否真的需要被多次、并发、原子地修改?还是只读快照?→ 若只读,
SET user:1001:profile '{"name":...}'完全够用 - 字段变更是否总伴随主结构其他字段一起更新?→ 若是,直接
HMSET扁平化更新更简单 - 有没有更轻量的替代?比如用
INCRBY改计数、HINCRBY改 Hash 内数值字段,避免 JSON 解析开销 - 业务是否已依赖 RedisJSON?若未部署该模块,硬上
JSON.SET会直接失败,不如老实用 List/Hash 拆分
真正值得写 Lua 的情况很少:比如“用户积分变更时,同步更新其所在所有排行榜的分数”,这种跨 key、带条件、需事务语义的操作。其他时候,拆 key 比嵌套更可控。










