在 Redis 日常使用中,我们常会遇到这样的场景:给一个键设置了过期时间后,又在过期前重复设置该键(且不指定过期时间)。这种操作会产生什么效果?是保留原过期时间,还是清除过期时间?本文将从实际现象出发,结合 Redis 源码,彻底揭开这一行为的底层逻辑。
一、直观现象:重复设置键会清除过期时间
先通过一组实验验证具体行为:
# 1. 设置键并指定过期时间(100秒)
127.0.0.1:6379> SET key "value" EX 100
OK
# 2. 查看剩余过期时间(约99秒)
127.0.0.1:6379> TTL key
(integer) 99
# 3. 过期前重复设置键(不指定过期时间)
127.0.0.1:6379> SET key "new value"
OK
# 4. 再次查看过期时间(返回-1,表示永不过期)
127.0.0.1:6379> TTL key
(integer) -1结论:当一个键已存在过期时间时,若在过期前用 SET 命令(不指定过期时间)重复设置,原过期时间会被清除,键变为 “永不过期”。
二、源码解析:过期时间的存储与清除机制
要理解这一现象,需从 Redis 中键的存储结构、过期时间的管理方式,以及 SET 命令的执行逻辑三方面分析。
1. 键的存储与过期时间的单独管理
Redis 中,所有键值对存储在数据库结构体 redisDb 中,其中:
dict *dict:存储键值对的哈希表(键为字符串,值为redisObject)。dict *expires:单独存储键的过期时间(键为字符串,值为过期时间戳,单位为毫秒)。
// server.h 中 redisDb 结构体定义
typedef struct redisDb {
dict *dict; // 键值对哈希表
dict *expires; // 过期时间哈希表
// ... 其他字段(如过期键淘汰相关)
} redisDb;
关键点:过期时间与键值对是 “分离存储” 的。一个键是否过期,取决于 expires 哈希表中是否存在对应的时间戳。
2. SET 命令的执行逻辑:会清除过期时间
SET 命令的核心处理函数是 setCommand(定义在 t_string.c 中),其关键逻辑包括:
检查键是否已存在。
存储新的键值对(覆盖原有值)。
清除该键在
expires哈希表中的记录(若存在)。
关键函数 dbDeleteExpire:从 expires 哈希表中删除键的过期时间,实现如下:
// db.c 中 dbDeleteExpire 函数
void dbDeleteExpire(redisDb *db, robj *key) {
// 从 expires 哈希表中删除键,返回是否删除成功
if (dictDelete(db->expires, key) == DICT_OK) {
// 若键存在过期时间,则更新数据库的过期键数量统计
server.stat_expired_keys--;
}
}
3. 为什么重复设置会清除过期时间?
结合上述源码可知:
当执行
SET key value(不指定过期时间)时,expire变量为false,触发dbDeleteExpire调用。dbDeleteExpire会从expires哈希表中删除该键的记录,导致键失去过期时间(即变为永不过期)。
即使键原本存在过期时间,只要重复设置时未指定新的过期时间,Redis 就会默认清除原有过期配置。
三、扩展:哪些命令会清除过期时间?
除了 SET 命令,其他覆盖式修改键值的命令(如 GETSET、SETNX 不带过期时间时)也会清除过期时间,核心原因是它们都会调用 dbDeleteExpire 函数。
例如 GETSET 命令(获取旧值并设置新值):
而 非覆盖式命令(如 APPEND、INCR)仅修改值的内容,不会清除过期时间:
bash
# 实验:APPEND 命令不影响过期时间
127.0.0.1:6379> SET key "a" EX 100
OK
127.0.0.1:6379> TTL key
(integer) 98
127.0.0.1:6379> APPEND key "b" # 追加内容(非覆盖)
(integer) 2
127.0.0.1:6379> TTL key # 过期时间仍存在
(integer) 95
原因:APPEND 等命令仅修改 redisObject 的值,不会调用 dbDeleteExpire,因此 expires 哈希表中的过期时间记录保持不变。
四、底层设计思考:为什么要这样实现?
Redis 设计 “重复设置键会清除过期时间” 的行为,本质是遵循 “显式操作优先” 原则:
当用户未指定过期时间时,Redis 认为 “覆盖设置键” 是一个新的操作,应默认使用键的 “默认状态”(永不过期)。
若需保留过期时间,用户需显式指定(如
SET key value EX 剩余秒数),避免隐式行为导致意外。
这种设计减少了歧义,让用户能更精确地控制键的生命周期。
总结
当一个带有过期时间的 Redis 键被重复设置(不指定过期时间)时:
键的旧值会被新值覆盖。
Redis 会从
expires哈希表中删除该键的过期时间记录。最终结果:键变为永不过期。
这一行为的底层逻辑由 setCommand 函数中的 dbDeleteExpire 调用实现,体现了 Redis 对 “显式操作” 和 “用户意图” 的尊重。在实际开发中,需注意这一特性,避免因误操作导致键长期留存占用内存