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

Redis SET NX EX 怎样避免先写后设过期

来源:17golang原创

时间:2026-10-26 01:49:36 117浏览 收藏

我在给短期幂等标记做缓存时,最先删掉的是“先 SET,再 EXPIRE”这段写法。两条命令之间只要发生超时、连接断开或进程退出,就可能留下没有过期时间的键。Redis 的 SET 本身已经支持 NX 和 EX,把条件写入、值和秒级 TTL 放在同一条命令里更容易维护。

要点速览
  • SET key value NX EX 60 只在 key 不存在时写入,并同时设置 60 秒过期时间。
  • 返回 OK 表示条件写入成功;空返回通常表示 NX 条件没有满足,不能当成异常重试。
  • EX 是秒,精度要求更高时使用 PX;续期已有键则应重新判断是否需要 XX 或 KEEPTTL。

先写后设过期,问题不在命令慢而在边界分开

下面这组命令看起来直观:

SET login:once:42 done
# 只有在上一条成功后才设置过期时间;两条命令之间存在断点
EXPIRE login:once:42 60

它的问题不是 SET 或 EXPIRE 单独不可靠,而是“写入成功”和“过期设置”不再属于同一个命令边界。客户端在第一条命令收到响应前断开、应用在第二条命令前退出,或者重试逻辑只重放了第一条命令,都可能留下一个永久键。对于幂等标记、短期锁提示或验证码状态,这个结果往往比一次写入失败更难排查。

生产代码里,我会先把目标写成一句话:只有不存在的 key 才能写入,并且成功写入必须带 TTL。这样就能直接对应到 SET 的条件和过期参数,而不是把两个动作交给调用方自行拼接。

SET NX EX 把三个约束放进一条命令

最小写法如下:

SET login:once:42 done NX EX 60
# NX 限制为“key 不存在才写”,EX 60 给成功写入附加 60 秒 TTL

NX 表示 key 不存在时才设置,EX 60 表示以秒设置过期时间。条件、值和生命周期参数都属于这条 SET 命令的语法,调用方不需要再补一条 EXPIRE。Redis 官方文档还提供 PX、EXAT 和 PXAT 等其他过期表达方式,但本题只需要相对秒数的 EX。

Redis SET NX EX 中 SET、NX、不存在条件、value、EX、秒数和 Redis key 的静态关系说明图
图1:结构说明图,展示 SET、NX、EX 与 key/value 的静态契约,不是终端截图或运行证据。

这条命令的关键价值是让“成功写入必须有过期时间”成为调用契约。它并不替你完成业务幂等设计:key 的命名、值的内容、TTL 长度和失败后的返回处理仍由应用负责。

OK、空返回和 TTL 分别说明什么

使用 NX 后,不要只判断客户端有没有抛异常,还要处理条件不满足的正常结果:

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/redis/go-redis/v9"
)

func claimOnce(ctx context.Context, rdb *redis.Client, key string) (bool, error) {
    // NX 负责只抢不存在的键,EX 让成功结果自带 60 秒生命周期。
    ok, err := rdb.SetNX(ctx, key, "done", 60*time.Second).Result()
    if err != nil {
        // 网络或服务端错误交给上层决定是否重试,不能当成“已存在”。
        return false, fmt.Errorf("set nx ex: %w", err)
    }
    // false 是条件未满足,不是 Redis 命令执行失败。
    return ok, nil
}

上面 Go 客户端的具体方法名会随客户端版本变化,命令语义仍然对应 SET key value NX EX seconds。示例中的 time.Duration 需要补充 time 包;这里保留调用结构,重点是区分 Redis 错误与“key 已存在”。

需要观察生命周期时,再单独读取 TTL:

TTL login:once:42
# 返回剩余秒数;查询 TTL 不会改变原有过期时间

一张判断表更适合放进排障清单:

结果含义应用动作
OK 或客户端布尔值 truekey 不存在,写入并设置 TTL继续执行一次性业务
空返回或布尔值 falseNX 条件未满足,通常是 key 已存在按已处理、重复请求或抢占失败处理
命令错误参数、连接或服务端问题记录真实错误,再按重试策略处理
TTL 为正数键仍有相对过期时间检查 TTL 是否符合业务预算
Redis SET NX EX 的 OK、空返回、key 已存在、TTL 和业务分支静态判断边界说明图
图2:结果边界说明图,展示响应值、键存在状态与 TTL 之间的判断关系,不是实际运行输出截图。

EX、PX、XX 和 KEEPTTL 不要混成一个意思

EX 使用秒,PX 使用毫秒。短期防重复场景通常先用秒表达预算,确实需要毫秒级窗口时才切换 PX,并在客户端统一时间单位,避免把 500 毫秒误写成 500 秒。

NX 是“只在不存在时设置”,适合首次占位;XX 是“只在已存在时设置”,更接近更新或续期已有键的条件。它们不是同一条业务语义的两个拼写。KEEPTTL 则表示更新值时保留已有 TTL,不等于给一个新键设置过期时间。

SET login:once:42 done NX PX 500
# 500 毫秒窗口:只对不存在的 key 写入

SET login:once:42 refreshed XX KEEPTTL
# 仅更新已存在的 key,并保留原 TTL;不要把它当作首次占位

还要留意一个常见误区:SET NX EX 只解决“单条命令内的条件写入和过期参数”这一边界。它不自动延长业务流程,也不保证后续处理一定成功;如果业务需要锁续期、持有者校验或释放保护,就需要另外设计令牌和值校验,不能靠重复发送同一条 SET 代替。

发布前用四项检查确认写法没有退化

  • 命令是否保留了 NX,并且 key 的命名能唯一对应一次业务尝试?
  • 成功写入是否在同一条 SET 中携带 EX 或 PX,没有回退成 SET 后再 EXPIRE?
  • 应用是否把空返回当作条件未满足,而不是把它当成 Redis 故障?
  • 需要更新已有键时,是否明确选择了 XX、KEEPTTL 或新的 TTL,而不是误用 NX?

如果只是做一次性占位,最终可以记成一句速查:SET key value NX EX seconds。这条命令的优势不是“少写一行代码”,而是把“条件成立”和“过期时间存在”放在同一个 Redis 命令契约里。

常见问题

SET NX EX 会不会覆盖已经存在的 key?

不会。NX 条件不满足时,命令返回空结果或客户端对应的 false,原 key 不会被这次写入覆盖。

为什么还要用 TTL 命令读取剩余时间?

SET NX EX 负责设置过期时间,TTL 负责观察剩余时间。读取 TTL 不会证明业务处理已经完成,只能帮助判断键的生命周期是否还在预算内。

已有键想续期应该继续用 NX 吗?

通常不应该。NX 面向不存在键;已有键的更新应根据业务选择 XX、新 TTL 或 KEEPTTL,并额外确认是否需要持有者校验。

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