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

这个边界也不包含应用拿旧值去调用其他服务、写业务数据库等后续动作。返回值可以作为应用逻辑输入,却不能据此声称跨系统操作已经原子完成。
空返回不等于写入失败
空回复描述的是“没有旧字符串可返回”,并不能脱离选项直接解读为失败。下面只列旧键不存在或旧值为字符串的情形;非字符串另行处理。
| 条件选项 | 原键状态 | 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 不会凭空得到有效期。

从回复类型判断结果,不盲目重试
接入代码应明确区分旧字符串、正常空回复和命令错误。旧字符串也可能是空字符串,甚至恰好等于文字 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/。
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习