Redis TTL 命令详细教程
TTL 用于获取 Key 的剩余生存时间(Time To Live),单位为秒。它是 Redis 通用 Key 操作命令,适用于检查缓存、会话、验证码、限流计数器和分布式锁等数据距离过期还有多久。
本文基于 Redis 通用 Key 操作介绍
TTL。Redis 命令本身不区分大小写,因此TTL、ttl和Ttl的效果相同;文档统一使用大写形式。TTL自 Redis 1.0 起可用。
资料合集:https://pan.quark.cn/s/10e98d308913、https://pan.quark.cn/s/f56bc69c5338
一、命令概览
1. 基本语法
redis
TTL key
参数说明:
| 参数 | 说明 |
|---|---|
key |
要查询剩余生存时间的 Key |
返回值:
| 场景 | 返回值 |
|---|---|
| Key 存在且设置了过期时间 | 剩余生存时间,单位为秒的整数 |
| Key 存在但没有过期时间 | -1 |
| Key 不存在或已经过期 | -2 |
2. 最简单的示例
先写入一个带过期时间的 Key,再查询剩余时间:
redis
SET mykey "Hello" EX 10
TTL mykey
返回值类似:
text
(integer) 9
返回值通常略小于 10,因为从设置 Key 到执行查询已经经过了一些时间,这是正常现象。
3. 命令特性
TTL是只读命令,不会修改 Key 的值或过期时间。- 时间单位是秒,返回值为整数。
- 时间不足 1 秒但 Key 仍存在时,通常返回
0。 - 时间复杂度为 O(1)。
TTL作用于整个 Key,适用于 String、Hash、List、Set、Sorted Set、Stream 等数据类型。
二、返回值详解
1. Key 存在且有过期时间
redis
SET session:user:1001 "active" EX 1800
TTL session:user:1001
返回一个正整数,例如:
text
(integer) 1799
这表示该 Key 预计还会保留约 1799 秒。
2. Key 存在但没有过期时间
redis
SET permanent-key "forever"
TTL permanent-key
返回:
text
(integer) -1
-1 表示 Key 存在,但没有设置过期时间,也不会自动过期。它不表示 Key 不存在。
3. Key 不存在
redis
TTL no-such-key
返回:
text
(integer) -2
Key 可能从未创建、已经被删除,或者已经过期并被 Redis 视为不存在。
4. 已过期的 Key
redis
SET short-key "value" EX 1
等待超过 1 秒后执行:
redis
TTL short-key
返回 -2。Redis 在访问 Key 时会执行惰性过期检查,因此已经过期的 Key 按不存在处理,而不是返回负的剩余秒数。
5. 剩余时间不足 1 秒
redis
SET short-key "value" PX 800
TTL short-key
PTTL short-key
返回可能类似:
text
TTL: (integer) 0
PTTL: (integer) 760
TTL 以秒为单位并向下取整,所以 0 可能表示 Key 仍存在但剩余时间不足 1 秒。需要毫秒精度时,应使用 PTTL。
三、TTL 与 PTTL 的区别
TTL 和 PTTL 都返回剩余生存时间,区别只有时间单位和精度:
| 特性 | TTL |
PTTL |
|---|---|---|
| 时间单位 | 秒 | 毫秒 |
| 精度 | 秒级,向下取整 | 毫秒级 |
| Key 无过期时间 | -1 |
-1 |
| Key 不存在 | -2 |
-2 |
| 适合场景 | 秒级倒计时、普通缓存 | 短时缓存、锁续期、精确限流 |
| 可用版本 | Redis 1.0+ | Redis 2.6+ |
1. 取整行为
redis
SET mykey "value" PX 1500
TTL mykey
PTTL mykey
如果查询时剩余约 1490 毫秒,结果可能是:
text
TTL: (integer) 1
PTTL: (integer) 1490
如果剩余约 800 毫秒,TTL 可能返回 0,而 PTTL 仍能返回约 800。
2. 判断条件的差异
text
TTL > 0 表示通常还剩至少 1 秒
TTL == 0 表示剩余不足 1 秒,或处于临界状态
TTL == -1 Key 存在但永久有效
TTL == -2 Key 不存在或已过期
如果业务阈值是 500 毫秒,不应使用 TTL == 0 判断,而应使用:
redis
PTTL mykey
然后在应用中判断 0 <= pttl < 500。
3. 选择建议
- 秒级精度足够时使用
TTL,语义直观。 - 需要亚秒级精度时使用
PTTL。 - 设置命令使用
PEXPIRE或SET ... PX时,查询通常也使用PTTL,避免单位换算错误。
四、与相关命令的区别
1. TTL 与 EXPIRE
EXPIRE 设置以秒为单位的相对过期时间,TTL 查询剩余秒数:
redis
SET cache:product:1001 "data"
EXPIRE cache:product:1001 3600
TTL cache:product:1001
EXPIRE 是写命令,返回设置是否成功;TTL 是读命令,返回剩余秒数。
2. TTL 与 PTTL
redis
TTL key # 返回剩余秒数
PTTL key # 返回剩余毫秒数
两者对不存在 Key 和永久 Key 的特殊返回值相同,都是 -2 和 -1。
3. TTL 与 EXPIRETIME
TTL 返回相对时间,EXPIRETIME 返回绝对过期时间戳(秒,Redis 7.0+):
redis
SET mykey "value" EX 3600
TTL mykey
EXPIRETIME mykey
| 需求 | 推荐命令 |
|---|---|
| 查询还剩多少秒 | TTL |
| 获取绝对过期时刻 | EXPIRETIME |
| 查询还剩多少毫秒 | PTTL |
| 获取绝对过期毫秒时间戳 | PEXPIRETIME |
4. TTL 与 EXISTS
EXISTS 只能判断 Key 是否存在,不能说明 Key 是永久有效还是带有过期时间:
redis
EXISTS mykey # 1 表示存在
TTL mykey # -1 表示永久,正数表示带 TTL
TTL 返回 -2 时,通常已经足以说明 Key 不存在;如果需要更清晰的存在性语义,可配合 EXISTS 使用。
5. TTL 与 PERSIST
PERSIST 移除 Key 的过期时间,使 Key 变为永久有效:
redis
SET mykey "value" EX 60
PERSIST mykey
TTL mykey
PERSIST 成功后,TTL mykey 返回 -1。
6. TTL 与 OBJECT IDLETIME
这两个命令含义完全不同:
redis
TTL mykey # 距离自动过期还有多久
OBJECT IDLETIME mykey # 距离上次访问经过了多少秒
一个 Key 可以永不过期但长时间未访问,此时 TTL 为 -1,而 OBJECT IDLETIME 可能很大。
五、过期时间如何变化
1. 使用 SET 设置过期时间
推荐在写入时原子设置 TTL:
redis
SET cache:key "value" EX 3600
SET short:key "value" PX 30000
带 EX 或 PX 的 SET 会同时设置值和过期时间。
2. 普通 SET 会清除过期时间
使用不带过期选项的 SET 覆盖 Key,会移除原来的过期时间:
redis
SET mykey "old" EX 60
SET mykey "new"
TTL mykey
此时返回 -1,因为第二次 SET 让 Key 变成永久有效。
如果要覆盖值但保留原有 TTL,可使用 Redis 支持的 KEEPTTL:
redis
SET mykey "new" KEEPTTL
TTL mykey
3. 修改集合内部数据通常保留 TTL
以下命令修改 Key 的内部内容,但不会刷新整个 Key 的过期时间:
redis
HSET user:1001 name "Alice"
LPUSH queue:jobs "job-1"
SADD tags redis
ZADD leaderboard 100 "player-1"
示例:
redis
HSET user:1001 name "Alice"
EXPIRE user:1001 3600
TTL user:1001
HSET user:1001 city "Shanghai"
TTL user:1001
第二次 TTL 会继续递减,而不会因为 HSET 自动恢复到 3600。
4. RENAME、COPY 与 TTL
RENAME 会把源 Key 的过期时间转移到新名称:
redis
SET old:key "value" EX 60
RENAME old:key new:key
TTL new:key
TTL old:key
new:key 保留剩余 TTL,old:key 不存在并返回 -2。
使用 COPY 时,应根据 Redis 版本和命令选项确认是否复制 TTL;需要明确控制副本过期属性时,不要仅凭经验推断,使用目标实例的命令文档确认。
六、不同数据类型中的 TTL
TTL 面向 Key,而不是某个数据结构中的单个元素:
redis
SET string:key "hello" EX 3600
TTL string:key
HSET hash:key name "Alice"
EXPIRE hash:key 3600
TTL hash:key
RPUSH list:key a b c
EXPIRE list:key 3600
TTL list:key
SADD set:key member-1 member-2
EXPIRE set:key 3600
TTL set:key
ZADD zset:key 100 member-1
EXPIRE zset:key 3600
TTL zset:key
对于普通 Key 级过期,整个 Key 到期后,其全部内容都会失效。Redis 7.4+ 还支持 Hash 字段级过期命令,但字段级 TTL 与 TTL key 的 Key 级 TTL 是两套不同语义。
七、只读、事务与并发
1. 只读特性
TTL 不修改值、过期时间或数据库内容,不会因为查询而刷新 TTL,也不会写入 AOF。它适合用于监控和状态检查。
2. 查询结果是时间快照
连续执行查询时,返回值会自然递减:
redis
TTL mykey
TTL mykey
两次结果的差异取决于实际经过的时间、命令处理时间和取整方式。不要要求返回值始终精确等于最初设置的秒数。
3. 先查询再操作存在竞态
下面的流程不是原子的:
redis
TTL lock:job:1
# 根据上一步结果决定是否续期
EXPIRE lock:job:1 30
在两条命令之间,Key 可能过期、被删除或被其他客户端修改。EXPIRE 对不存在的 Key 返回 0,不会自动创建 Key,但业务判断仍可能受到竞态影响。
需要原子地检查并续期时,可使用 EXPIRE 的条件选项(Redis 7.0+)或 Lua 脚本。对于分布式锁,还必须校验锁的持有者,不能只依据 TTL 续期。
4. 在事务中使用
redis
MULTI
TTL mykey
EXISTS mykey
EXEC
事务中的命令会排队,在 EXEC 时按顺序执行。需要注意,MULTI 与 EXEC 之间其他客户端仍可能修改 Key,因此把多个命令放入事务并不等同于在排队期间锁定数据。
5. Pipeline 批量查询
查询多个 Key 的 TTL 时,可以通过客户端 Pipeline 减少网络往返:
text
TTL cache:1
TTL cache:2
TTL cache:3
Pipeline 只是批量发送命令,不会自动提供跨命令的事务原子性;如果业务需要原子性,应明确使用事务或 Lua 脚本。
八、常见客户端用法
1. redis-cli
bash
redis-cli SET session:user:1001 "active" EX 1800
redis-cli TTL session:user:1001
redis-cli TTL no-such-key
redis-cli SET permanent "forever"
redis-cli TTL permanent
2. Python(redis-py)
python
import redis
client = redis.Redis(
host="localhost",
port=6379,
decode_responses=True,
)
client.set("session:user:1001", "active", ex=1800)
ttl = client.ttl("session:user:1001")
print(ttl) # 约为 1800
client.set("permanent", "forever")
print(client.ttl("permanent")) # -1
print(client.ttl("no-such-key")) # -2
3. Node.js(node-redis)
javascript
import { createClient } from "redis";
const client = createClient();
await client.connect();
await client.set("session:user:1001", "active", { EX: 1800 });
console.log(await client.ttl("session:user:1001")); // 约为 1800
await client.set("permanent", "forever");
console.log(await client.ttl("permanent")); // -1
console.log(await client.ttl("no-such-key")); // -2
await client.quit();
4. Java(Jedis)
java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;
try (Jedis jedis = new Jedis("localhost", 6379)) {
jedis.set("session:user:1001", "active", SetParams.setParams().ex(1800));
long ttl = jedis.ttl("session:user:1001");
System.out.println(ttl); // 约为 1800
jedis.set("permanent", "forever");
System.out.println(jedis.ttl("permanent")); // -1
System.out.println(jedis.ttl("no-such-key")); // -2
}
客户端通常会把 -1 和 -2 原样作为整数返回。业务代码应显式区分特殊值,不要把所有非正数都当成同一种状态。
九、典型业务场景
1. 缓存刷新
redis
SET cache:product:1001 "{...json...}" EX 3600
TTL cache:product:1001
应用可以在 TTL 低于某个阈值时提前刷新缓存,减少缓存同时失效造成的请求突增。需要精确到毫秒时使用 PTTL。
2. 会话有效期
redis
SET session:abc123 "user:1001" EX 1800
TTL session:abc123
登录态检查时,-2 通常表示会话不存在或已过期,正数表示还剩多少秒,-1 则说明会话没有自动过期保护,需要重点排查。
3. 验证码或一次性令牌
redis
SET otp:user:1001 "582714" EX 300
TTL otp:user:1001
消费令牌时先验证值,再删除 Key。不要只检查 TTL 就认为令牌有效,还需要校验令牌内容、用户和用途。
4. 限流窗口
redis
INCR rate:user:1001
EXPIRE rate:user:1001 60 NX
TTL rate:user:1001
NX 可以避免每次计数时都重置窗口。生产代码还应考虑计数与首次设置过期时间之间的原子性,必要时使用 Lua 脚本。
5. 分布式锁观察
redis
SET lock:order:1001 "client-id" NX EX 30
TTL lock:order:1001
TTL 可以用于观察锁剩余时间,但不能单独用于安全释放或续期。释放锁时应校验 value 是否属于当前客户端,并使用原子脚本删除。
6. 延迟任务辅助检查
redis
SET delay:task:1001 "payload" EX 60
TTL delay:task:1001
TTL 只能告诉应用还剩多少时间,不能保证在过期瞬间收到通知。需要可靠的延迟任务,应使用 Streams、专业消息队列或正确配置并监控 Keyspace Notifications。
十、过期机制与 TTL
1. 惰性过期
客户端访问 Key 时,Redis 会检查它是否已经过期。如果已过期,Redis 会将其按不存在处理,因此 TTL 返回 -2。
2. 定期过期
Redis 会在后台周期性抽样检查设置了 TTL 的 Key,并清理其中已过期的 Key。没有访问的过期 Key 可能在物理上暂时仍占用内存,但逻辑上已经不可用。
3. 过期与内存淘汰不是一回事
Key 可能因为过期而删除,也可能因为达到 maxmemory 后被淘汰。使用 TTL 时若 Key 返回 -2,只能说明当前不可见,不能仅凭此命令确定是自然过期、主动删除还是内存淘汰。
4. 主从复制
在主从架构中,过期判断和删除主要由主节点完成,主节点会把删除结果传播给副本。副本上的 TTL 可能因时钟、复制延迟和读写节点不同而出现短暂差异;关键业务的状态判断应以正确的权威节点为准。
十一、性能与使用建议
- 时间复杂度为 O(1),单次执行开销很低。
TTL是只读命令,适合查询单个 Key 的状态。- 正确区分三类返回值:正数表示带 TTL,
-1表示永久有效,-2表示不存在或已过期。 - 不要把
TTL == 0直接等同于 Key 不存在;剩余不足 1 秒时 Key 仍可能存在。 - 需要毫秒精度时使用
PTTL,不要通过TTL * 1000猜测精确剩余时间。 - 大量 Key 查询时使用 Pipeline,并控制轮询频率,避免监控程序制造不必要的 QPS。
- 不要使用
KEYS *配合 TTL 做生产环境全量扫描,应使用SCAN分批遍历。 - 设置值和过期时间时优先使用
SET key value EX seconds或SET key value PX milliseconds,避免两步操作造成 Key 没有 TTL。 - 对缓存设置 TTL 时加入随机偏移,可减少大量 Key 同时到期造成的缓存雪崩。
- 分布式锁的安全性不能只依赖
TTL,释放、续期和所有权校验应使用原子方案。
十二、常见问题排查
问题 1:TTL 返回 -2
可能原因:
| 可能原因 | 排查方法 |
|---|---|
| Key 从未创建 | 使用 EXISTS key 确认 |
| Key 已过期 | 检查设置 TTL 的时间和业务日志 |
| Key 被删除 | 检查应用中的 DEL、UNLINK 等操作 |
| 连接了错误的实例 | 检查主机、端口、认证和集群路由 |
| 选择了错误的逻辑数据库 | 检查是否执行过 SELECT |
| Key 名称不一致 | 使用 SCAN MATCH 检查实际名称 |
问题 2:TTL 返回 -1
-1 表示 Key 存在但没有过期时间。常见原因包括:
- 创建 Key 时没有使用
EX、PX、EXAT或PXAT。 - 后续使用不带过期选项的
SET覆盖了原值。 - 某个流程调用了
PERSIST。 - 应用把用户输入的过期时间单位误判为秒或毫秒,导致设置逻辑没有执行。
redis
EXISTS mykey
TTL mykey
TYPE mykey
问题 3:TTL 返回 0
0 通常表示 Key 仍存在,但剩余时间不足 1 秒。用 EXISTS 或 GET 进一步确认:
redis
TTL mykey
EXISTS mykey
如果需要精确判断剩余时间,改用:
redis
PTTL mykey
问题 4:TTL 比设置值小
这通常是正常现象。从执行 SET 或 EXPIRE 到执行 TTL 的过程中,时间已经流逝。偏差明显过大时,检查是否有其他客户端重新设置过期时间、是否发生了网络延迟,或是否查询了副本节点。
问题 5:设置了 TTL 但 Key 仍然永久存在
检查设置 TTL 后是否又执行了普通 SET:
redis
SET mykey "old" EX 60
SET mykey "new"
TTL mykey
第二个 SET 会清除 TTL。若需要保留 TTL,使用 SET ... KEEPTTL,或在覆盖时显式重新设置过期时间。
问题 6:大批量 Key 到期导致延迟
大量 Key 使用相同 TTL 可能在同一时间集中失效,引起缓存击穿和删除压力。可以为 TTL 加入随机抖动,并通过监控、限流、互斥重建或逻辑过期等方式降低峰值。
十三、完整练习
下面的示例覆盖带 TTL、永久 Key、不存在 Key、秒毫秒精度、更新 TTL 和移除 TTL 等场景。请在测试数据库执行;FLUSHDB 会清空当前逻辑数据库中的全部 Key。
redis
FLUSHDB
# 1. 基本用法
SET demo:temp "data" EX 10
TTL demo:temp
# 2. 永久 Key
SET demo:forever "value"
TTL demo:forever
# 3. 不存在的 Key
TTL demo:not-exist
# 4. 秒级与毫秒级精度对比
SET demo:short "value" PX 1500
TTL demo:short
PTTL demo:short
# 5. 使用 EXPIRE 修改 TTL
SET demo:renew "session"
EXPIRE demo:renew 30
TTL demo:renew
EXPIRE demo:renew 60
TTL demo:renew
# 6. SET 覆盖会清除 TTL
SET demo:renew "new-value"
TTL demo:renew
# 7. KEEPTTL 保留现有 TTL
SET demo:keep "old" EX 60
SET demo:keep "new" KEEPTTL
TTL demo:keep
# 8. PERSIST 移除 TTL
SET demo:persist "value" EX 60
PERSIST demo:persist
TTL demo:persist
# 9. 集合数据类型使用 Key 级 TTL
HSET demo:hash name "Alice" age 30
EXPIRE demo:hash 3600
TTL demo:hash
HSET demo:hash city "Shanghai"
TTL demo:hash
# 10. 已过期 Key
SET demo:gone "value" EX 1
# 等待 1 秒以上后执行:
# TTL demo:gone
# EXISTS demo:gone
# 11. 与 EXISTS 组合判断
EXISTS demo:temp
TTL demo:temp
EXISTS demo:forever
TTL demo:forever
预期结果:
demo:temp的TTL返回小于或等于10的正数。demo:forever的TTL返回-1。demo:not-exist的TTL返回-2。demo:short的TTL可能返回0或1,PTTL返回剩余毫秒数。demo:renew第二次EXPIRE后,TTL 被更新为约60秒。- 普通
SET demo:renew后,TTL 返回-1。 SET ... KEEPTTL后,demo:keep仍保留剩余 TTL。PERSIST demo:persist后,TTL 返回-1。HSET修改 Hash 字段不会自动刷新 Key 的 TTL。demo:gone过期后,TTL返回-2,EXISTS返回0。
十四、命令速查表
| 需求 | 命令 | 精度或结果 |
|---|---|---|
| 获取剩余生存时间 | TTL key |
秒 |
| 获取剩余生存时间 | PTTL key |
毫秒 |
| 设置相对过期时间 | EXPIRE key seconds |
秒 |
| 设置相对过期时间 | PEXPIRE key milliseconds |
毫秒 |
| 按时间戳设置过期 | EXPIREAT key timestamp |
秒时间戳 |
| 按时间戳设置过期 | PEXPIREAT key milliseconds-timestamp |
毫秒时间戳 |
| 获取过期时间戳 | EXPIRETIME key |
Redis 7.0+,秒 |
| 获取过期时间戳 | PEXPIRETIME key |
Redis 7.0+,毫秒 |
| 移除过期时间 | PERSIST key |
变为永久 Key |
| 原子写入并设置秒级 TTL | SET key value EX seconds |
推荐 |
| 原子写入并设置毫秒级 TTL | SET key value PX milliseconds |
推荐 |
| 覆盖值时保留 TTL | SET key value KEEPTTL |
按版本确认支持 |
| 判断 Key 是否存在 | EXISTS key |
0 或 1 |
| 增量遍历 Key | SCAN cursor [MATCH pattern] |
避免阻塞 |
总结
TTL 的核心作用是查询 Key 的剩余生存时间,基本语法为:
redis
TTL key
使用时重点注意:
- 正数表示剩余秒数,
-1表示 Key 存在但没有过期时间,-2表示 Key 不存在或已过期。 TTL以秒为单位并向下取整,剩余不足 1 秒时返回0不一定代表 Key 不存在。- 需要毫秒级精度时使用
PTTL,不要把TTL的结果简单乘以 1000。 - 普通
SET会清除已有 TTL,SET ... KEEPTTL可在覆盖值时保留 TTL。 TTL是只读快照,先查询再操作存在并发竞态;锁续期、释放和所有权校验应使用原子方案。- 设置值和过期时间时优先使用
SET ... EX或SET ... PX,避免两步操作留下永久 Key。