Redis GETSET 更新值后 TTL 变成 -1
GETSET 会清除键原有过期时间。按 Redis 版本选择 SET KEEPTTL 或 GET KEEPTTL,保留截止时间,并处理键已消失和无有效期的分支。
Redis 字符串键原本设置了过期时间,执行 GETSET 更新值后,TTL 却变成 -1。值虽然更新成功,键也变成了没有过期时间的持久键。成功执行的 GETSET 会清除原有过期设置;它返回旧值,并不表示保留旧值附带的有效期。
这个场景需要先区分“保留原截止时间”和“从更新时重新计时”。例如原本只剩 20 秒的缓存,更新后仍应在原时间到期,就不能简单重新设置为 60 秒。
用 TTL 确认丢失的是过期设置
在正确的实例和逻辑数据库中,对实际目标键读取:
TYPE demo:ttl
TTL demo:ttl
demo:ttl 是示例键名。本文针对字符串键;其他类型不能直接套用字符串覆盖操作。Redis 2.8 起,TTL 返回 -1 表示键存在但没有过期时间,-2 表示键不存在。两者处理方式不同:键可能已经正常过期,不能看到负数就重新写入。
若更新前有正的 TTL,成功 GETSET 后变成 -1,应检查应用实际发送的写入命令。普通 SET 没有指定保留过期时间时,同样可能把原过期设置清掉。客户端方法名称不够,应核对参数和服务器版本。
Redis 6.0 起:不需要旧值时使用 KEEPTTL
已经确认目标键存在、只允许更新现有键时,可以采用:
SET demo:ttl value-b XX KEEPTTL
KEEPTTL 保留键当时已有的过期时间,XX 则限制为键存在才写入。如果键已经消失,命令不创建新键,应用要按未写入的返回结果处理。
这两个选项并不负责建立新的有效期。如果目标键本来没有过期时间,更新后仍然没有;如果业务允许创建新键,去掉 XX 又会改变不存在时的行为,不能不加判断地替换。
KEEPTTL 从 Redis 6.0 开始提供。它与 EX、PX、EXAT、PXAT 不能在同一条 SET 中混用;要么保留现有期限,要么明确指定新的期限。
Redis 6.2 起:同时需要返回旧值
需要保留 GETSET 返回旧字符串的用途时,可使用 Redis 6.2 起支持的 SET 的 GET 选项:
SET demo:ttl value-b XX GET KEEPTTL
服务器在同一条命令中处理读旧值、写新值和保留期限。不要假定返回值仍是普通 SET 的 OK:GET 会改变返回内容,应用必须同步调整处理逻辑。已有值不是字符串时会报错并中止操作,不能用它替换其他类型的更新命令。
GETSET 从 Redis 6.2 起已被标记为弃用。旧 Redis 2.8 环境不能使用这些新选项;客户端升级也不会让旧服务器识别它们。
为什么不先读 TTL,再把秒数写回
客户端先读取 TTL,再执行 SET ... EX,中间可能经过排队、网络延迟或其他客户端修改。把读到的剩余秒数从后一次写入重新开始计算,会改变截止时间;原键也可能在两条命令之间过期。
因此,这种拆成两次请求的办法不能当作原子替代。旧版本必须继续运行时,需要另行审核服务端原子逻辑,覆盖键不存在、无过期时间和并发修改等分支,不能照搬“读 TTL 后无条件 EXPIRE”的片段。
已经被 GETSET 清掉的截止时间,也不会被后来加上的 KEEPTTL 自动找回。应根据应用保存的业务期限或允许的缓存重建规则恢复,不能给所有异常键猜一个统一 TTL。
在隔离键上验收
下面的示例只用于 Redis 6.2 及以上的可丢弃测试数据,不要使用业务键名:
SET demo:ttl-check value-a EX 60
TTL demo:ttl-check
SET demo:ttl-check value-b XX GET KEEPTTL
GET demo:ttl-check
TTL demo:ttl-check
在 60 秒内完成时,更新应返回旧值,读取应得到新值,剩余时间继续减少而非变成 -1 或重新回到完整时长。再验证键已过期与原本无 TTL 的分支,确认应用不会把它们误当作正常续期。
发布前保留原客户端调用与配置。回退代码并不会恢复已经丢失的截止时间;若旧调用仍会清除 TTL,应先控制这类写入,再按已确认的业务规则处理受影响键。


