Redis命令:MSETNX

Redis MSETNX 命令详细教程

MSETNX 仅当指定的全部 Key 都不存在时,一次性创建所有 String 键值对。任意一个 Key 已存在,就全部不写;不是"跳过已有 Key,补齐其他 Key"。

资料合集:https://pan.quark.cn/s/10e98d308913https://pan.quark.cn/s/f56bc69c5338

一、语法与返回值

redis 复制代码
MSETNX key value [key value ...]
项目 说明
可用版本 Redis 1.0.1 起
参数 至少一组 Key/Value,成对提供
成功返回 整数 1,全部写入;不是写入数量
条件不满足 整数 0,整批不写
官方复杂度 O(N),N 为 Key 数量
ACL 类别 @write@string@slow

全部不存在的检查和批量写入是同一原子操作,可用于创建分散在多个 Key 的逻辑对象;RESP2 和 RESP3 的成功/条件失败回复均为整数。参数、权限、内存、部署路由等错误仍可能产生错误回复,不应与整数 0 混为一谈。$TRAE_REF

二、从成功到条件失败

在测试 redis-cli 交互会话执行。以下是预期结果,未连接实例实测;删除前确认专用 Key 可清理。

redis 复制代码
DEL tutorial:{msetnx}:a tutorial:{msetnx}:b tutorial:{msetnx}:c
MSETNX tutorial:{msetnx}:a "Hello" tutorial:{msetnx}:b "there"
MSETNX tutorial:{msetnx}:b "new" tutorial:{msetnx}:c "world"
MGET tutorial:{msetnx}:a tutorial:{msetnx}:b tutorial:{msetnx}:c

第一次写入返回 1。第二次因 b 已存在而返回 0:b 保持 "there",c 根本没有创建;最终结果依次为 "Hello""there"nil

三、存在性的准确含义

状态 结果
所有 Key 都不存在 返回 1,创建全部 String
任意 Key 已存在且为 String 返回 0,整批不写
任意 Key 存在但为 Hash/List 等 返回 0,不因该类型而报 WRONGTYPE
任意 Key 的值为 "" 它仍然存在,返回 0
已有值与拟写入值相同 仍然返回 0,不比较 Value
Key 已过期且执行时判定不存在 按不存在处理;仍须其他 Key 也不存在
新写 Value 为 "" 合法,成功后该 Key 存在
没有键值对或参数不成对 返回参数数量错误

不存在与"存在但为空"不能混淆,先读 GET 再判断空值会引入错误逻辑和竞态。重复 Key 也不应当作防重复机制:在常规实现中,全部预检查通过后依次写入,同名 Key 后值覆盖前值;建议发送前按业务规则去重,不依赖重复参数行为。

四、TTL 与不支持的选项

命令只接受键值对,没有 EXPXKEEPTTL 等选项。成功创建的 Key 无过期时间,TTL-1;返回 0 时不修改已有 Key 的值与到期时刻,但剩余 TTL 会随真实时间自然减少。它与 MSET 的区别是根本不覆盖已存在的数据。$TRAE_REF

特别注意 MSETNX a A EX 60 会把 EX 当作另一个 Key,要求 a 和 EX 都不存在,成功时写入 a=A、EX=60,而不是为 a 设置 TTL。不要直接拼接 SET 风格的选项。

MSETNX 再由客户端逐个 EXPIRE 会留出进程中断窗口。把 MSETNX 与无条件 EXPIRE 简单放进事务也不安全:即使前者返回 0,后面的过期命令仍会执行,可能改变他人的 Key。条件设置过期必须检查写入结果。

五、带过期的条件创建

下面是固定两个 Key、固定 60 秒 TTL 的 Lua 教学示例。在允许所有相关命令、参数已验证且正常执行的前提下,仅成功创建后才设置过期;失败则不会修改已存在 Key。

redis 复制代码
EVAL "local ok = redis.call('MSETNX', KEYS[1], ARGV[1], KEYS[2], ARGV[2]); if ok == 1 then redis.call('EXPIRE', KEYS[1], 60); redis.call('EXPIRE', KEYS[2], 60); end; return ok" 2 tutorial:{msetnx}:lease:a tutorial:{msetnx}:lease:b token-a token-b

脚本的 KEYS 必须是两个不同的业务 Key;ARGV 提供对应 Value,示例中的 token 只是演示文本。成功返回 1,冲突返回 0;第二次连续执行(在 60 秒内且无其他改动)通常返回 0。可用 TTL 检查剩余时间。

Lua 原子执行不代表运行时错误回滚:若后续命令因权限等原因报错,前面成功的写入不会自动撤销。生产中应预先验证参数、权限、资源限制及失败恢复,不能把短脚本视为所有故障下的事务提交协议。

六、并发、事务与重试

不要用客户端 EXISTS → 判断 → MSET 模拟本命令,检查后到写入前,别的客户端可能先写入。MSETNX 将这两个步骤放在原子边界内;多个客户端竞争同一组 Key,在无删除、过期、淘汰或故障切换的前提下,最多一个成功创建。

多个 SET ... NX 即使放在 Pipeline 或 MULTI/EXEC 内,也会逐个判断各自条件,可能只创建一部分;它们不等价于 MSETNX 的整批条件。事务能够防止执行期间交错,但不会因某条返回条件失败而自动撤销其他写入。

超时不表示未成功。第一次可能成功却丢失回复,重试返回 0 只能说明"现在至少有一个 Key 存在",不能证明是本请求创建或他人创建。需要可靠重试时写入唯一请求标识,并用适当的原子逻辑验证归属,不能失败后无条件 DEL 全部 Key。

七、不要直接当分布式锁

MSETNX 只提供一批不存在检查和创建,不自带租约、续期、所有者校验、释放协议或故障切换安全保障。无 TTL 的占位可能永久残留,盲目清理可能删除他人的新占位。

单资源短期占位通常从 SET key unique-token NX PX timeout 开始设计,并需原子验证 token 后释放。多资源锁、业务唯一性和跨系统事务远超本命令语义;重要约束仍应由权威数据库的唯一约束或专门协调机制承担。

八、集群与性能

Redis Open Source Cluster 中,全部 Key 必须在同一哈希槽;跨槽会返回 CROSSSLOT。官方多 Key 规则还将 Redis Software 集群中的 MSETNX 列为同槽操作,不能因为某部署的 MSET 支持跨槽就假设本命令也支持。$TRAE_REF

本文的 tutorial:{msetnx}:atutorial:{msetnx}:b 具有同一 hash tag。把请求拆给不同槽分别执行,会破坏"全部满足才全部写入"的条件,不能作为语义等价的自动重试方案。若强关联字段很多,考虑单个 Hash 或一个整体对象,但必须重新设计其初始化条件。

复杂度 O(N),同时关注参数总字节数、内存、复制与持久化成本。对于高冲突初始化,不要无退避地循环尝试;失败并不表示需要立即重试,也不要为检查存在性发送大量无意义的 Value。

九、Python 客户端示例

假设已安装 redis-py,并有可访问的本地测试 Redis。官方接口 msetnx(mapping) 接收映射;redis-py 常规解析为 True/False,与协议整数 1/0 对应。$TRAE_REF

python 复制代码
import redis
r = redis.Redis(host="localhost", port=6379, decode_responses=True)
a, b, c = [f"tutorial:{{msetnx}}:py:{x}" for x in ("a", "b", "c")]
r.delete(a, b, c)  # 仅测试专用 Key
try:
    print(r.msetnx({a: "A", b: "B"}))  # True
    print(r.msetnx({b: "new", c: "C"}))  # False
    print(r.mget([a, b, c]))  # ['A', 'B', None]
finally:
    r.delete(a, b, c)

Jedis 对应 jedis.msetnx("k1", "v1", "k2", "v2"),返回 long 1 或 0;不要把返回值解释成成功 Key 数。空映射应在客户端业务层直接处理,而非发送空参数命令。

十、命令对比与使用场景

需求 方案
整组 Key 首次初始化,任何冲突都放弃 MSETNX
无条件覆盖整组值 MSET
每个 Key 独立初始化,允许部分成功 多次 SET ... NX
单 Key 条件创建并指定过期 SET key value NX EX seconds
同一 Hash 的普通多字段更新 HSET,不是整对象不存在判断
查看当前整组值 MGET;类型不符也可能返回 nil

典型用途是为新逻辑对象建立一组关联 String、一次性初始化测试夹具、以任意冲突为失败条件的整组占位。不适合"补齐缺失配置":已有一个配置就会阻止所有其他配置写入。

十一、综合练习与排查

以下练习覆盖空字符串、非 String 类型、TTL 保留和已过期 Key。示例 Key 使用专用命名空间,开始前确认可删除。

redis 复制代码
DEL tutorial:{msetnx}:x tutorial:{msetnx}:y
SET tutorial:{msetnx}:x "" EX 300
MSETNX tutorial:{msetnx}:x X tutorial:{msetnx}:y Y
EXISTS tutorial:{msetnx}:y
TTL tutorial:{msetnx}:x
DEL tutorial:{msetnx}:x
RPUSH tutorial:{msetnx}:x item
MSETNX tutorial:{msetnx}:x X tutorial:{msetnx}:y Y
TYPE tutorial:{msetnx}:x
DEL tutorial:{msetnx}:x
SET tutorial:{msetnx}:x old
EXPIREAT tutorial:{msetnx}:x 1
MSETNX tutorial:{msetnx}:x X tutorial:{msetnx}:y Y
MGET tutorial:{msetnx}:x tutorial:{msetnx}:y
TTL tutorial:{msetnx}:x
DEL tutorial:{msetnx}:x tutorial:{msetnx}:y

预期:空串已存在时返回 0,y 不存在,x 保留到期时刻;List 已存在时仍返回 0,类型为 list;过去的绝对时间令 x 被删除,再次写入返回 1,值为 "X""Y",TTL 为 -1。原有 TTL 检查需要在 300 秒内完成。其他章节的 a/b/c 和 lease Key 可按完整名称单独清理。

现象 排查方向
只想补写缺失项却什么也没写 任意 Key 已存在即整批失败,属设计语义
空字符串也阻止创建 空值不等于不存在
返回 0 却不知道冲突项 可用 TYPE/EXISTS 诊断,但后续查询不是失败时刻的快照
成功后永不过期 命令没有 TTL 选项,需要另行原子设计
事务失败分支改变了旧 TTL 不要无条件执行后续 EXPIRE
重试返回 0 不能判断首次是否成功,须按请求标识核验
CROSSSLOT 检查 hash tag,不要拆批冒充整批原子创建

总结

MSETNX 全部 Key 不存在才全部写入,成功返回 1,任意存在则返回 0 且全部不写。 它检查的是存在性,不是类型或值;新 Key 无 TTL。涉及过期、锁、重试与跨槽操作时,需要额外设计,不能把条件批量创建等同于完整业务事务。

相关推荐
wdfk_prog3 小时前
ROS教程08:从 TransportTCP::connect() 追到 TCPROS Connection Header、序列化与 Socket 数据传输
运维·缓存·docker·容器·ros
浅念-4 小时前
Redis基础详解:单线程模型、String与Hash数据结构
服务器·数据库·redis·sql·mysql·nosql数据库·nosql
张某布响丸辣4 小时前
附件下载的安全边界:路径白名单、NAS 哨兵文件与 Redis Lua 一次性令牌
java·redis·redis lua·nas哨兵
Wang's Blog6 小时前
Java 接入Redis: 使用Jedis操作Redis
java·服务器·redis
月临水7 小时前
Redis 缓存预热、穿透、击穿、雪崩
redis
重生之我是Java开发战士7 小时前
【Redis】初识Redis
redis
是Dream呀8 小时前
Harness 工程:让 Agent 真正把任务做完
人工智能·分布式·缓存·agent
Wang's Blog8 小时前
Java 项目实战: 外卖平台-数据库环境搭建与十一张表结构解析
java·服务器·redis
喜欢的名字被抢了9 小时前
01-Redis 基础篇:从定位到 Key 管理
redis·教程