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

Redis SET 的 GET 选项怎么原子取得旧值

来源:17golang原创

时间:2026-09-28 03:03:00 413浏览 收藏

直接使用 SET key new-value GET。Redis 会在一次命令中把新值写入字符串键,并把覆盖前的旧字符串作为响应返回;键原来不存在时返回 nil。这个写法把“读取旧值”和“写入新值”放进同一个原子操作,避免客户端先发 GET、再发 SET 时被其他写入插队。

官方文档:https://redis.io/docs/latest/commands/set/

分开的 GET 和 SET 留下了并发窗口

如果业务只在单机测试,先读再写看起来没有问题:

# 先读取旧库存
redis-cli GET inventory:sku-42

# 再覆盖为新库存
redis-cli SET inventory:sku-42 18

问题在于这其实是两条独立命令。客户端拿到旧值后、发出 SET 前,另一个客户端可能已经改过同一个键。第一个客户端最终覆盖的是最新值,却拿着更早的旧值继续做审计、补偿或通知,读写语义就被拆散了。

MULTI/EXEC 可以把多个命令排队执行,但为了“覆盖并返回旧值”专门创建事务没有必要。Redis 6.2 起,SET 自带的 GET 选项已经能直接表达这个动作。

一条 SET 同时完成写入和旧值返回

最小写法只是在普通 SET 后面增加 GET:

# 先准备一个旧值
redis-cli SET inventory:sku-42 21

# 写入 18,并在同一条命令中返回旧值 21
redis-cli SET inventory:sku-42 18 GET

# 核对键当前保存的新值
redis-cli GET inventory:sku-42

第二条命令的响应是旧值 21,第三条命令读到的是新值 18。关键点不是少一次网络往返,而是旧值读取与新值写入属于同一条 Redis 命令,中间不会出现另一个命令可见的插入位置。

客户端、SET 命令、GET 选项、字符串键、新值与旧值响应的静态关系图
图1:SET 与 GET 选项的静态调用关系说明图。命令携带新值写入字符串键,同时把覆盖前的旧值作为响应;此图不是运行截图。

这条命令的复杂度是 O(1)。在 RESP2 中,旧值以 bulk string 返回;原键不存在时返回 null bulk string。不同客户端库会把 nil 映射为各自的空响应、空对象或特定错误,因此业务代码要按客户端文档判断“键不存在”,不要把空字符串和 nil 混在一起。

三个返回边界必须先想清楚

键原来不存在

命令仍会创建键并写入新值,但因为不存在旧值,所以 GET 返回 nil:

# 确保示例键不存在
redis-cli DEL session:token:42

# 创建键;响应为 nil,而键的新值已经写入
redis-cli SET session:token:42 token-b GET

# 核对新值
redis-cli GET session:token:42

因此,nil 表示“命令执行前没有这个键”,不表示 SET 一定失败。

旧值不是字符串

GET 选项要求旧值能按字符串返回。如果键当前保存的是列表、集合、哈希等非字符串类型,Redis 会返回 WRONGTYPE 错误,并中止这次 SET;旧数据不会被覆盖。这个行为比普通 SET 更严格:普通 SET 可以无视原类型直接覆盖,而 SET GET 必须先取得旧字符串。

# 创建一个列表类型的键
redis-cli RPUSH queue:jobs job-1

# 旧值不是字符串,这次写入会报错并中止
redis-cli SET queue:jobs replaced GET

GET 与 NX、XX 一起使用

条件选项负责决定是否写入,GET 负责返回命令执行前的值。当前官方返回值说明明确指出:指定 GET 后,只要旧键存在,就会返回旧值,无论条件最终是否让新值写入;旧键不存在则返回 nil。也就是说,单靠响应里的旧值不能证明条件写入成功。

Redis 7.0 起才允许 NX 与 GET 一起使用。如果应用需要组合这些选项,要先核对服务端版本,并在条件写入场景中用额外业务状态或后续读值确认结果,不能沿用“返回非 nil 就代表写入成功”的判断。

TTL 要明确选择保留还是重设

成功执行普通 SET 会清除键原来的过期时间。增加 GET 只改变返回值,不会自动保留 TTL。如果这个键原来会过期,更新时必须明确选择以下三种语义之一:

写法TTL 结果适合场景
SET key value GET清除旧 TTL,变为持久键新值应长期保留
SET key value GET KEEPTTL保留已有 TTL只换值,不改变原到期时刻
SET key value GET EX 60重新设置 60 秒 TTL更新值时重置有效期
SET key value GET PX 1500重新设置 1500 毫秒 TTL需要毫秒级有效期
# 保留会话键原有的到期时间,同时取得旧令牌
redis-cli SET session:token:42 token-c GET KEEPTTL

# 写入新缓存值,并把有效期重新设为 60 秒
redis-cli SET cache:profile:42 v3 GET EX 60
SET GET、默认写入、KEEPTTL、EX、PX 与新旧 TTL 的静态关系图
图2:SET GET 与 TTL 选项关系静态说明图。KEEPTTL 保留原 TTL,EX 或 PX 设置新过期时间,未给过期选项时成功写入会清除旧 TTL;此图不是运行截图。

EX、PX、EXAT、PXAT 与 KEEPTTL 属于同一组互斥的过期选项,一条 SET 只能选择其中一种。不要同时写 KEEPTTL 和 EX,希望 Redis “先保留再延长”;那不是合法组合。

从 GETSET 迁移到 SET GET

旧代码常用 GETSET key value 完成相同的基础替换。Redis 官方 SET 文档说明,SET 的选项能够替代 GETSET。迁移时可以直接改成:

# 旧写法:写入新值并返回旧值
redis-cli GETSET counter:snapshot 1024

# 新写法:Redis 6.2 及以上使用 SET GET
redis-cli SET counter:snapshot 1024 GET

新写法的优势是可以继续组合 KEEPTTL 或 EX、PX 等过期选项,命令语义集中在 SET 上。兼容边界也很明确:GET 选项从 Redis 6.2 开始提供;如果集群中仍有更早版本的节点,必须先完成版本核对和升级,再替换客户端命令。

采用时按这张清单检查

  • 只在确实需要旧值时使用 GET:不需要旧值的普通覆盖继续用 SET,语义更直接。
  • 把 nil 当成“此前不存在”:它不等同于空字符串,也不自动代表写入失败。
  • 先确认旧键类型:非字符串旧值会让 SET GET 报错并中止。
  • 显式决定 TTL:默认清除、KEEPTTL 保留、EX/PX 重设,三者不要混淆。
  • 条件写入不要只看旧值:组合 NX 或 XX 后,GET 返回值不能单独证明是否写入。
  • 服务端至少 Redis 6.2:NX 与 GET 的组合还需要 Redis 7.0 或更高版本。

相关问题

SET GET 返回的是新值还是旧值?

返回命令执行前保存的旧字符串。新值已经写进键中,之后再 GET 才会读到新值。

键不存在时为什么返回 nil,但键又被创建了?

GET 描述的是旧状态。旧键不存在,所以没有旧值可返回;与此同时,SET 仍按正常语义创建键并写入新值。

SET GET 会自动保留过期时间吗?

不会。成功 SET 默认清除旧 TTL。需要保持原到期时刻时增加 KEEPTTL,需要重置有效期时选择 EX、PX、EXAT 或 PXAT。

为什么不直接继续用 GETSET?

SET GET 能表达相同的基础替换,还能与 SET 的过期和条件选项组合。新项目优先统一到 SET 语法,旧项目迁移前确认 Redis 版本和客户端响应映射即可。

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