Redis HSETNX 命令详细教程
HSETNX仅在字段不存在时设置其值,字段已存在则不做任何操作。它返回 0 或 1,是字段级的“不存在才写入”原语。
资料合集:https://pan.quark.cn/s/10e98d308913、https://pan.quark.cn/s/f56bc69c5338
一、概览与语法
HSETNX key field value| 项目 | 说明 |
|---|---|
| 数据类型 | Hash |
| 支持版本 | Redis 2.0.0 起 |
| key | Hash 的 Key,不存在时自动创建 |
| field | 字段名,仅在其不存在时设置 |
| value | 要写入的值 |
| 返回值 | 1 表示字段是新的且已设置;0 表示字段已存在且未做操作 |
| 时间复杂度 | O(1) |
| ACL | @write、@hash、@fast |
| 命令标记 | write、denyoom、fast |
官方说明:如果 Key 不存在,会创建一个持有 Hash 的新 Key;如果字段已存在,该操作不产生任何效果。$TRAE_REF
二、基础示例
以下命令在测试实例的 redis-cli 中执行。文中结果是预期说明,未实际连接 Redis 运行。示例沿用官方示例。
DEL tutorial:{hsetnx}:myhash HSETNX tutorial:{hsetnx}:myhash field Hello HSETNX tutorial:{hsetnx}:myhash field World HGET tutorial:{hsetnx}:myhash field HLEN tutorial:{hsetnx}:myhash预期结果:第一次 HSETNX 返回1并创建 Key;第二次针对同一字段返回0,值未被覆盖;HGET 返回"Hello";HLEN 返回1。这组示例完整体现了 HSETNX 的核心语义:第二次写入被静默忽略,不报错。
三、返回值语义与存在性判定
| 场景 | 返回值 |
|---|---|
| 字段不存在(含 Key 不存在) | 1,写入成功 |
| 字段已存在 | 0,未做任何修改 |
判断“字段是否存在”的标准是字段名本身,与字段值无关:
| 字段当前状态 | HSETNX 结果 |
|---|---|
| 字段不存在 | 返回 1,写入 |
字段值为空字符串"" | 返回 0,不写入 |
字段值为"0" | 返回 0,不写入 |
字段值为"null" | 返回 0,不写入 |
| 字段已到期(7.4 及以上) | 视同不存在,返回 1 |
因此“值为空”或“值为假”都不等于“字段不存在”。如果业务把空值当作未设置,需要在应用层额外约定,而不能依赖 HSETNX 的判断。
四、与 HSET、SET NX 的区别
| 命令 | 判断粒度 | 返回值 | 说明 |
|---|---|---|---|
| HSETNX | 单个字段 | 0 或 1 | 字段不存在才写入 |
| HSET | 无判断 | 新增字段数 | 无条件覆盖 |
| SET NX | 整个 Key(String 类型) | OK 或空值 | Key 不存在才设置 |
| HSETEX FNX | 字段集合(8.0 起) | 0 或 1 | 所有字段都不存在才写入 |
HSETNX 一次只能处理一个字段,没有批量版本。需要多个字段都“不存在才写入”时,应使用 HSETEX 的 FNX 选项(Redis 8.0 起),或在脚本中组合判断。
另外注意返回值形态不同:SET NX 成功返回OK、失败返回空值,而 HSETNX 返回整数 1 或 0,不能混用判断逻辑。
五、TTL 与覆盖语义
HSETNX 只在字段不存在时写入,因此它不会覆盖已有字段,也就不会清除已有字段的 TTL。新建的字段默认不带过期时间。
DEL tutorial:{hsetnx}:ttl HSET tutorial:{hsetnx}:ttl a 1 HEXPIRE tutorial:{hsetnx}:ttl 300 FIELDS 1 a HTTL tutorial:{hsetnx}:ttl FIELDS 1 a HSETNX tutorial:{hsetnx}:ttl a 999 HGET tutorial:{hsetnx}:ttl a HTTL tutorial:{hsetnx}:ttl FIELDS 1 a HSETNX tutorial:{hsetnx}:ttl b 2 HTTL tutorial:{hsetnx}:ttl FIELDS 1 b预期结果:设置 TTL 后 HTTL 返回正数;HSETNX 对已存在的 a 返回0,HGET 仍为"1",HTTL 仍为正数,说明值和 TTL 都未被改动;对不存在的 b 返回1,b 的 HTTL 为-1,即新字段默认永久有效。字段级 TTL 需要 Redis 7.4 或更高版本。
六、边界情况与错误处理
| 场景 | 行为 |
|---|---|
| Key 不存在 | 创建 Hash,写入字段,返回 1 |
| 字段已存在 | 返回 0,不修改值与 TTL |
| Key 是 String、List 等非 Hash | 报 WRONGTYPE 错误 |
| 参数个数不足或多余 | 报语法错误,HSETNX 只接受 key、field、value |
| 字段名或值为空字符串 | 合法,正常处理 |
| 内存达到上限且策略禁止写入 | 命令带 denyoom 标记,写入被拒绝 |
HSETNX 无法对已存在字段做条件更新。需要“值等于某条件时才更新”时,应使用 Lua 脚本或带 WATCH 的事务。
七、原子性与并发价值
HSETNX 的真正价值在于原子性。用“先 HEXISTS 判断、再 HSET 写入”实现同样逻辑时,两次调用之间存在窗口期,其他客户端可能已抢先写入,导致后写者覆盖先写者的数据。
非原子写法(存在竞态): 客户端 A: HEXISTS h f -> 0 客户端 B: HEXISTS h f -> 0 客户端 A: HSET h f A 客户端 B: HSET h f B (B 覆盖了 A 的写入) 原子写法: 客户端 A: HSETNX h f A -> 1 客户端 B: HSETNX h f B -> 0 (B 未写入,A 的值被保留)因此 HSETNX 适合初始化默认值、抢占式分配唯一标识、保证某字段只被设置一次等场景。
八、客户端示例
前提为已安装 redis-py 并准备好本地测试实例。
importredis r=redis.Redis(host="localhost",port=6379,decode_responses=True)k="tutorial:{hsetnx}:python"try:r.delete(k)print(r.hsetnx(k,"field","Hello"))# True,字段不存在,写入成功print(r.hsetnx(k,"field","World"))# False,字段已存在,未写入print(r.hget(k,"field"))# Hello# 值为空字符串也算“已存在”r.hset(k,"empty","")print(r.hsetnx(k,"empty","x"))# Falsefinally:r.delete(k)r.close()Java(Jedis)示例,返回 long:
try(Jedisjedis=newJedis("localhost",6379)){System.out.println(jedis.hsetnx("tutorial:{hsetnx}:java","field","Hello"));// 1System.out.println(jedis.hsetnx("tutorial:{hsetnx}:java","field","World"));// 0System.out.println(jedis.hget("tutorial:{hsetnx}:java","field"));// Hellojedis.del("tutorial:{hsetnx}:java");}九、典型场景与使用建议
典型用途:初始化配置项的默认值、保证某字段只被写入一次(例如首次登录时间)、抢占式领取标识、幂等写入。
使用建议:
| 建议 | 说明 |
|---|---|
| 用返回值判断是否写入成功 | 1 表示写入,0 表示已被占用 |
| 不要用空值表达“未设置” | 空字符串也算已存在 |
| 需要批量时改用 HSETEX FNX | HSETNX 只支持单字段 |
| 需要设置 TTL 时配合 HEXPIRE | 或使用 HSETEX(8.0 起) |
| 需要条件更新已有值 | 使用 Lua 脚本或 WATCH 事务 |
HSETNX 写入的新字段不带过期时间。若业务要求“首次写入并自动过期”,需要额外调用 HEXPIRE,或用 HSETEX 一条命令完成。
十、练习、排错与总结
练习:新建tutorial:{hsetnx}:exercise,执行HSETNX ... a 1预期返回1;再次执行HSETNX ... a 2预期返回0且 HGET 仍为"1";用HSET ... empty ""写入空字符串后执行HSETNX ... empty x,预期返回0,理解“空值也算已存在”;最后用 HTTL 确认 a 无 TTL。
排错要点:返回 0 不是错误,表示字段已存在;返回值是整数 1/0 而不是OK,不要与 SET NX 的判断逻辑混用;报 WRONGTYPE 时用 TYPE 检查类型;报参数错误时确认只传了 key、field、value 三个参数;需要批量“不存在才写入”时应改用 HSETEX 的 FNX。清理使用DEL tutorial:{hsetnx}:myhash tutorial:{hsetnx}:ttl tutorial:{hsetnx}:exercise。速记:2.0 起支持、单字段原子条件写入、返回 1/0、不覆盖已有字段、不改变已有 TTL、新字段默认永久。