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

Redis SET 怎样在写入时返回旧值

来源:17golang原创

时间:2026-10-18 10:42:22 100浏览 收藏

要在 Redis 写入新字符串的同时拿到旧字符串,使用 SET key new_value GET。GET 是 SET 的一个选项,不是紧接着再执行一次 GET:无条件替换成功时,回复的是替换前的值;原键不存在时返回空回复,但新值仍然会被创建。

例如发布配置时,希望把开关从 old 改成 new,同时知道被替换的版本。普通 SET 返回 OK,不能直接提供旧值;拆成先读后写又会留下并发间隙。这里仅讨论单键替换与返回语义,不把它扩展成分布式锁或完整的审计系统。

官方地址:https://redis.io/docs/latest/commands/set/。基础 GET 选项从 Redis 6.2 开始支持,NX 与 GET 组合从 7.0 开始支持。以下为供读者在隔离测试实例运行的命令示例,本文未执行它们,结果描述均为预期。

写入后拿到的不是新值

先排除最常见的误判:返回 old 不代表新值没有保存。带 GET 后,回复契约由普通写入状态改为旧值;需要看新值时,后续单独 GET 只能说明那次读取时的状态。

# 仅在隔离实例中使用专用样例键,先设置已知初值
redis-cli SET article:setget:mode old

# 一次替换为 new,预期回复旧字符串 old,而不是 OK 或 new
redis-cli SET article:setget:mode new GET

# 无其他写入者时,预期读取到 new
redis-cli GET article:setget:mode

若业务代码还用“回复必须等于 OK”判断成功,加上 GET 后就会误报。应改为按客户端的字符串、空值及错误接口处理,不能继续沿用普通 SET 的布尔返回假设。

把两次请求改成一次替换

独立的 GET 和 SET 之间,其他客户端可能已经改写这个键。即使把两条命令放进 pipeline,也不能仅凭批量发送就把它们变成一个不可分割的替换操作。

SET GET 将旧值读取与新值替换放在同一个命令的原子边界内。图中的“键”“旧字符串”“新字符串”和“旧值回复”是理解这个契约的对象;它不意味着响应到达应用之前,其他客户端不能再次写入。

SET GET 命令边界内键、新旧字符串与旧值回复的静态关系
图1:静态说明图。区分命令参数、键的内容和返回对象;不是时间轴、执行截图或持久化保证。

这个边界也不包含应用拿旧值去调用其他服务、写业务数据库等后续动作。返回值可以作为应用逻辑输入,却不能据此声称跨系统操作已经原子完成。

空返回不等于写入失败

空回复描述的是“没有旧字符串可返回”,并不能脱离选项直接解读为失败。下面只列旧键不存在或旧值为字符串的情形;非字符串另行处理。

条件选项原键状态GET 的回复本次写入
无条件不存在空回复创建新值
无条件已有字符串旧字符串覆盖
XX不存在空回复不创建
XX已有字符串旧字符串覆盖
NX,Redis 7.0+不存在空回复创建新值
NX,Redis 7.0+已有字符串旧字符串不覆盖
# 隔离示例:mode 已由上一节设为字符串 new;该组合要求 Redis 7.0+
# NX 条件不成立,预期仍回复旧值 new,但不会写入 candidate
redis-cli SET article:setget:mode candidate NX GET

# 无其他写入者时,预期仍为 new
redis-cli GET article:setget:mode

因此“拿到字符串就说明替换成功”是错的。对 NX GET,旧字符串意味着已有键阻止了创建。对无条件 SET GET,空回复则可以伴随成功创建。判断必须绑定本次命令的确切选项,而不是只做非空判断。

示例中的后续 GET 是教学观察手段,不是生产并发下对上一次命令结果的可靠反推:它可能读到别人稍后写入的值。

旧值不是字符串,就不能继续替换

普通 SET 可以覆盖其他类型;带 GET 时则需要读出旧字符串。如果键保存的是列表等非字符串对象,Redis 返回类型错误并中止这次 SET,而不是先覆盖再报告错误。

# 在隔离实例确认此专用键尚不存在后,建立一个列表
redis-cli LPUSH article:setget:list item

# 预期类型错误,不能把错误转换成“没有旧值”
redis-cli SET article:setget:list replacement GET

# 没有其他操作时,预期类型仍为 list
redis-cli TYPE article:setget:list

遇到这种情况应检查键命名和数据模型,不要为了拿到旧值而先删除真实业务键。应用要把命令错误与正常的空回复分开;驱动返回错误时,不进入“首次创建”的业务分支。

需要保留有效期时显式写出来

GET 控制回复内容,不负责保留 TTL。成功的普通 SET 替换会清除原有效期;要保留剩余有效期,加 KEEPTTL;要从本次写入重新计时,用 EX 或 PX。保留与重设是不同策略,不要同时指定冲突的有效期选项。

# 仅在隔离实例初始化一个有效期为 120 秒的字符串
redis-cli SET article:setget:ttl v1 EX 120

# 取旧值并保留当前剩余有效期,不会重置回 120 秒
redis-cli SET article:setget:ttl v2 GET KEEPTTL

# 及时观察应为剩余秒数;实际值随执行间隔变化
redis-cli TTL article:setget:ttl

# 取旧值并从这次成功写入起重新设置 120 秒
redis-cli SET article:setget:ttl v3 GET EX 120

TTL 返回 -1 表示键存在但没有有效期,-2 表示键不存在。若观察时键已经过期,也可能得到 -2;不能把命令之间的时间消耗当成选项失效。不存在的键使用 KEEPTTL 不会凭空得到有效期。

GET 回复契约与条件选项、有效期策略的静态分组
图2:静态说明图。回复契约、条件选项与有效期策略是不同维度,旧值和 TTL 不能互相替代。

从回复类型判断结果,不盲目重试

接入代码应明确区分旧字符串、正常空回复和命令错误。旧字符串也可能是空字符串,甚至恰好等于文字 OK;不要根据日志显示文本或语言中的真假值猜测协议类型。某些客户端把正常空回复映射为特殊返回状态,具体按所用版本的驱动约定处理。

网络超时尤其不能直接当作“Redis 没写入”:服务端可能已完成替换,只是回复未送达。无条件重试后拿到的旧值,可能已是第一次写入的新值,甚至是其他客户端写入的值。需要明确的重试和结果确认策略,不能宣称一次替换天然具备业务幂等性。

GETSET 还能用吗?

官方从 Redis 6.2 将 GETSET 标记为弃用,并建议新代码或迁移时使用 SET 的 GET 选项。不要把弃用误写成已删除;老环境的兼容性需按实际版本决定,不能把现代选项下发给不支持的服务端。

能先比较旧值,再决定是否覆盖吗?

GET 选项本身只改变返回值,不等同于“只有值等于某个预期版本才更新”。若有比较后更新的业务要求,应另选与版本匹配的条件机制;不能在应用收到旧值后,才把已经完成的无条件覆盖解释成条件更新。

为什么只看到空白输出?

命令行的原始输出模式可能不便区分空回复与空字符串。先检查输出模式和客户端返回类型,再结合条件选项解释;不要仅凭一行空白宣布写入失败。

处理顺序可以收敛为:确认服务端版本和 GET 选项支持情况,固定命令选项,区分回复与错误,再检查 TTL 策略。这样才不会把“旧值为空”“条件未满足”和“类型错误”混成同一个问题。

官方参考:SET 选项与返回语义 https://redis.io/docs/latest/commands/set/;GETSET 原子语义与弃用说明 https://redis.io/docs/latest/commands/getset/;TTL 返回值 https://redis.io/docs/latest/commands/ttl/。

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