登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  Redis

RedisJSON 怎样只在字段不存在时写入

来源:17golang原创

时间:2026-10-17 23:00:42 447浏览 收藏

RedisJSON 的条件写入关键不在“字段名像不像新字段”,而在 JSONPath 是否命中了位置:NX 只在路径没有匹配时写入,XX 只在路径已经匹配时写入。要实现“字段不存在才补写”,通常选 JSON.SET key $.field value NX;要实现“只更新已有字段”,则选 JSON.SET key $.field value XX。

官方地址:https://redis.io/docs/latest/commands/JSON.SET/

下面的示例只围绕一个 RedisJSON 文档展开,重点看路径、条件和返回值的对应关系。命令中的 (nil) 表示条件没有满足或路径无法创建,不等于 Redis 服务异常。

NX 与 XX 到底判断什么

NX 与 XX 判断的是路径匹配结果,而不是简单判断 Redis 键是否存在。

条件路径要求适合的意图条件不满足
NX路径没有匹配位置首次创建、只补缺失字段返回 (nil)
XX路径至少匹配一个位置只更新已有字段返回 (nil)
不带条件按路径直接创建或替换明确允许覆盖由路径本身决定
RedisJSON 文档、JSONPath、路径匹配结果与 NX XX 条件的静态关系示意图
图1:RedisJSON 条件写入的路径匹配结构示意图,不是截图或运行证据。

例如文档中已有 $.profile.name,执行 JSON.SET user:1 $.profile.name "李四" NX 时,路径已经命中,NX 不会覆盖它;同一个路径配合 XX 才是更新语义。

用根路径 NX 创建一次性文档

当 Redis 键还不存在时,新 JSON 文档只能从根路径 $ 或旧式根路径 . 建立。根路径没有匹配位置,因此适合配合 NX 做“只初始化一次”。

# 第一次只在文档不存在时创建根对象
redis-cli JSON.SET user:100 $ '{"name":"小林","level":1}' NX

# 第二次仍然使用 NX,根路径已经命中,因此不会覆盖原文档
redis-cli JSON.SET user:100 $ '{"name":"新值","level":9}' NX

# 读取根文档,确认初始化值仍然保留
redis-cli JSON.GET user:100 $

预期语义是第一次返回 OK,第二次返回 (nil)。这里的关键是把“初始化整个文档”限制在根路径;如果把新键与 $.name 直接组合,RedisJSON 没有父文档可以承载这个成员。

用字段路径 NX 补写缺失成员

当父对象已经存在时,NX 可以用于补写最后一级、尚未出现的对象成员。它不会替你创建缺失的中间对象,所以 $.profile.nickname 能否成功,取决于 profile 是否已经是对象。

# 先准备一个已经存在的父对象
redis-cli JSON.SET user:101 $ '{"profile":{"name":"小周"}}'

# nickname 尚未命中,NX 允许补写最后一级成员
redis-cli JSON.SET user:101 $.profile.nickname '"zhou"' NX

# nickname 已经命中,再次使用 NX 会返回 nil,不覆盖原值
redis-cli JSON.SET user:101 $.profile.nickname '"changed"' NX

# 读取 profile,查看补写结果
redis-cli JSON.GET user:101 $.profile

这里要区分两件事:profile 是已存在的父对象,nickname 是待创建的最终成员。只有后一段缺失时,NX 才有机会返回 OK。

用 XX 更新已存在字段

如果业务要求“只有字段已经存在才允许更新”,应使用 XX。这样可以把拼写错误、数据结构不完整或初始化遗漏暴露为条件未满足,而不是静默增加一个新字段。

# 准备一个已有 status 字段的文档
redis-cli JSON.SET order:100 $ '{"status":"pending","retry":0}'

# status 已命中,XX 允许更新它
redis-cli JSON.SET order:100 $.status '"paid"' XX

# missing 字段没有命中,XX 拒绝这次写入
redis-cli JSON.SET order:100 $.missing '1' XX

# 读取 status,确认更新目标是已有字段
redis-cli JSON.GET order:100 $.status

XX 返回 (nil) 时,调用方可以选择记录“字段未初始化”、回退到初始化流程,或者把请求当作业务冲突处理。不要把所有 nil 都当成网络故障重试,否则重试不会改变路径命中条件。

新键、根路径与嵌套字段的创建边界

最容易混淆的是“最后一级成员可以创建”和“中间路径可以自动创建”并不是一回事。RedisJSON 可以在已有父对象中创建最终成员,但不会跨过不存在的中间对象。

RedisJSON 根路径、新文档、已有父对象和缺失中间路径的静态边界示意图
图2:RedisJSON 新文档与嵌套字段创建边界示意图,不是截图或运行证据。
数据状态路径推荐判断结果重点
键不存在$NX可以创建整个根文档
父对象存在,最终成员不存在$.profile.nicknameNX可以补写最终成员
父对象不存在$.profile.nickname先初始化父对象中间路径无法创建,通常返回 (nil)
最终成员已经存在$.profile.nicknameXX可以更新;配合 NX 则拒绝覆盖
# 文档只有 name,没有 profile
redis-cli JSON.SET user:102 $ '{"name":"小赵"}'

# profile 这个中间对象不存在,不能直接跨层创建 nickname
redis-cli JSON.SET user:102 $.profile.nickname '"zhao"' NX

# 先建立父对象,再补写最终成员
redis-cli JSON.SET user:102 $.profile '{}' NX
redis-cli JSON.SET user:102 $.profile.nickname '"zhao"' NX

如果键本身不存在且路径不是根路径,RedisJSON 还会拒绝用嵌套路径直接建立新文档。工程上更稳妥的做法是把“根文档初始化”和“已有对象补字段”拆成两个明确的写入边界。

把返回值接入调用方决策

条件写入的返回值很简单:成功通常是 OK,条件不满足或路径无法创建时是 (nil),值或路径格式错误则是错误回复。调用方应按这三类分支处理。

# 用 shell 变量保留返回值,便于区分条件失败与命令错误
result=$(redis-cli JSON.SET user:101 $.profile.nickname '"new"' NX)

# 只把 nil 当成“目标路径已有值或暂时不可创建”
if [ "$result" = "(nil)" ]; then
  echo "条件未满足:保留已有字段或补齐父对象"
elif [ "$result" = "OK" ]; then
  echo "写入成功"
else
  echo "命令或路径错误:${result}"
fi

在客户端库中,具体返回类型可能表现为字符串、布尔值或空值,但判断原则不变:先识别条件未满足,再决定是否走初始化、更新或冲突分支,不要在没有改变文档状态时无限重试。

常见问题

NX 能不能保证整个 Redis 键只写一次?

对根路径 $ 使用 NX,可以表达“这个 JSON 文档只初始化一次”。如果只对某个字段使用 NX,它保护的是该字段路径,不会阻止同一文档的其他字段被写入。

字段不存在时用 NX,为什么还是返回 nil?

先检查父对象是否存在、是否真的是对象,以及中间路径是否缺失。最终成员缺失可以创建,但中间对象不会自动补齐。

更新字段应该选 NX 还是 XX?

只更新已存在字段选 XX;只在字段不存在时补写选 NX。若允许无条件覆盖,则不要把条件失败误判成 Redis 故障。

JSON.SET 的路径写法应该用 $ 还是 .?

根路径可以使用 $ 或 .。新文章和新代码建议统一使用 $,嵌套字段用类似 $.profile.nickname 的 JSONPath 表达。

小结

记住一句话:NX 看路径没有命中,XX 看路径已经命中。新键从根路径初始化,已有父对象才能补最后一级成员,缺失的中间路径需要先单独建立。把这三条边界写进调用方分支,RedisJSON 的“只写一次”和“只更新已有字段”就不会混在一起。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>