RedisJSON中JSON.SET的用法小结
作者:Hello.Reader
本文全面解析 RedisJSON 插件的核心指令JSON.SET,涵盖语法规则、典型用法、易错点和性能优化建议,感兴趣的可以了解一下
1 · 为什么要写这篇文章?
在结构化数据存储场景里,传统的字符串键值对已难以满足灵活查询与字段级更新的需求。RedisJSON 插件为 Redis 带来了对原生 JSON 文档的读写支持,其中最核心也最常用的指令就是 JSON.SET。本文将带你彻底吃透 JSON.SET 的各个细节——从语法、时间复杂度到多路径批量更新,再到在生产环境中容易踩到的坑和性能调优方法,并提供一套 Go-Redis 的完整示例代码,帮助你快速落地。
2 · RedisJSON 与 JSON.SET 概览
| 特性 | 说明 | |
|---|---|---|
| 插件版本 | RedisJSON ≥ 1.0 (推荐 2.x 与 Redis 7.x+ 搭配使用) | |
| 指令 | `JSON.SET key path value [NX | XX]` |
| 功能 | 按 JSONPath 将 value 写入 key 对应的 JSON 文档 | |
| ACL 标记 | @json @write @slow | |
| 时间复杂度 | O(M + N) —— M 为原值大小,N 为新值大小 × 匹配路径数 |
3 · 语法详解
JSON.SET <key> <path> <value> [NX | XX]
| 参数 | 必填 | 说明 |
|---|---|---|
| key | ✔ | Redis 中的键;不存在时必须从根路径写入 |
| path | ✔ | JSONPath 表达式;$ 或 . 代表根 |
| value | ✔ | 合法 JSON 字符串,或原样字符串(无需额外引号) |
| NX | 仅当目标不存在时写入 | |
| XX | 仅当目标已存在时覆盖 |
小贴士:NX 与 XX 不仅作用于 Redis 键,也作用于 JSON 文档内部键。这点经常被忽略!
4 · JSONPath 规则速查
- 根路径:$ 或 .
- 点式:$.address.city
- 数组下标:$.items[0]
- 切片:$.items[1:3](RedisJSON 2.x 支持)
- 通配:$..price 匹配所有层级的 price 字段
- 多路径:$..a 与 $..b 可一次性传入多条
注意:JSON.SET 不支持在一次调用中写入 不同 的值到多条路径;如果传入多路径,所有匹配点都会被同一个 value 覆盖。
5 · 返回值与错误处理
| 情况 | 返回 |
|---|---|
| 写入成功 | OK |
| 路径不存在且无法创建 | (nil) |
| NX/XX 条件不满足 | (nil) |
| 根键不存在但路径不是 $ | (error) ERR new objects must be created at the root |
生产环境中推荐显式检查返回值,而不要只依赖 err == nil。示例见 § 9。
6 · 典型用法示例
6.1 替换已有字段
redis> JSON.SET doc $ '{"a":2}'
OK
redis> JSON.SET doc $.a 3
OK
redis> JSON.GET doc $
"[{\"a\":3}]"
6.2 追加新字段
redis> JSON.SET doc $ '{"a":2}'
OK
redis> JSON.SET doc $.b 8
OK
redis> JSON.GET doc $
"[{\"a\":2,\"b\":8}]"
6.3 一次性批量更新多路径
redis> JSON.SET doc $ '{"f1":{"a":1},"f2":{"a":2}}'
OK
redis> JSON.SET doc $..a 3
OK
redis> JSON.GET doc
"{\"f1\":{\"a\":3},\"f2\":{\"a\":3}}"
6.4 结合 NX / XX 条件
# 仅当字段不存在时写入 redis> JSON.SET user:1 $.nickname '"neo"' NX OK # 第一次成功 redis> JSON.SET user:1 $.nickname '"smith"' NX (nil) # 条件未满足
7 · 易踩坑汇总
| 坑 | 现象 | 解决方案 |
|---|---|---|
| 键不存在却写子路径 | 抛错 ERR new objects must be created at the root | 第一次写必须用根路径 $ |
| 值未用 JSON 字符串包裹 | 若忘记加引号:JSON.SET $.name neo ⇒ 解析失败 | 非数值 / 布尔需双引号或 'neo' |
| NX/XX 作用域误解 | 误以为只检查 Redis 键 | 其实同时检查 JSON 内目标字段 |
| 字符串过长 | 超出 512 MB 限制 | 拆分存储或压缩再存 |
8 · 性能调优与并发安全
- Pipeline / MULTI
连续多次 JSON.SET 可使用 Pipelining 或事务批量发送,减少 RTT。 - 避免大文档频繁写
时间复杂度包含原值大小 M:越大的 JSON,写一次越慢。可将热点字段拆分为独立键或使用 JSON.NUMINCRBY 等增量指令。 - 合理选择 NX/XX
在幂等场景下使用 NX 或 XX 可避免无谓的重写,降低写放大。 - 监控慢日志
Redis 会将执行时间 > slowlog-log-slower-than 的指令记录;JSON 操作本质属 @slow 类别,应重点关注。 - Lua/RedisGears 乐观锁
高并发写同一字段,可结合 WATCH/MULTI 或 Lua 脚本实现 CAS。
9 · Go-Redis 完整示例(可直接运行)
依赖:Go ≥ 1.22、github.com/redis/go-redis/v9
// 文件 main.go
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/redis/go-redis/v9"
)
var ctx = context.Background()
type Profile struct {
Name string `json:"name"`
Age int `json:"age"`
Tags []string `json:"tags"`
}
func must(err error) {
if err != nil {
log.Fatalf("fatal: %v", err)
}
}
func main() {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
defer rdb.Close()
// ---------- 1. 写入根文档 ----------
profile := Profile{Name: "Alice", Age: 27, Tags: []string{"gopher", "redis"}}
raw, _ := json.Marshal(profile)
if res, err := rdb.Do(ctx, "JSON.SET", "user:1001", "$", raw).Text(); err != nil {
must(err)
} else {
fmt.Println("set root:", res) // OK
}
// ---------- 2. 更新子字段 ----------
if res, err := rdb.Do(ctx, "JSON.SET", "user:1001", "$.age", 28, "XX").Text(); err != nil {
must(err)
} else if res == "" {
fmt.Println("XX unmet, age not updated")
} else {
fmt.Println("update age:", res) // OK
}
// ---------- 3. 读取文档 ----------
data, err := rdb.Do(ctx, "JSON.GET", "user:1001", "$").Result()
must(err)
fmt.Println("profile =", data.(string))
// ---------- 4. 错误示例 ----------
if _, err := rdb.Do(ctx, "JSON.SET", "user:1002", "$.name", "\"Bob\"").Result(); err != nil {
fmt.Println("expected error:", err)
}
// Output:
// set root: OK
// update age: OK
// profile = [{"name":"Alice","age":28,"tags":["gopher","redis"]}]
// expected error: ERR new objects must be created at the root
}
运行后,你将看到成功写入、条件更新、读回以及错误处理的完整流程。
10 · 进阶话题
| 方向 | 价值 |
|---|---|
| 与 RediSearch 集成 | 对 JSON 字段建立二级索引,支持全文检索与聚合分析 |
| 存储版本化配置 | 使用 NX 写入新版本,配合 JSON.NUMINCRBY 维护版本号 |
| 灰度发布 | 通过数组/对象结构保存多线路配置信息,再用 JSON.DEL 快速回滚 |
| Streaming JSON | 将大文档拆分到 Stream 或 List,再按需合并到 RedisJSON |
11 · 总结
- JSON.SET 是 RedisJSON 最核心的写入指令,先掌握根写入,再逐步学习多路径与条件写。
- 时间复杂度与文档大小 线性相关,因此避免频繁重写大 JSON。
- 正确使用 NX/XX 可实现幂等更新、乐观锁等高级场景。
- 在 Go-Redis 中通过 Do(ctx, "JSON.SET", …) 可无缝调用,务必检查返回值而非只关注 err。
- 生产环境要结合 慢日志、Pipeline、事务 等手段进行性能与可靠性保障。
到此这篇关于RedisJSON中JSON.SET的用法小结的文章就介绍到这了,更多相关RedisJSON JSON.SET内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
