Redis PEXPIRE 命令详细教程
PEXPIRE 用于为 Key 设置以毫秒为单位的过期时间(生存时间,TTL),到期后 Redis 会自动删除该 Key。它是 EXPIRE 的毫秒级版本,适合需要亚秒级精度的场景,如短时分布式锁、高频限流、精确倒计时等。
本文基于 Redis 通用 Key 操作介绍
PEXPIRE。Redis 命令本身不区分大小写,因此PEXPIRE、pexpire和Pexpire的效果相同;文档统一使用大写形式。PEXPIRE自 Redis 2.6 起可用,NX/XX/GT/LT条件选项自 Redis 7.0 起可用。
资料合集:https://pan.quark.cn/s/10e98d308913、https://pan.quark.cn/s/f56bc69c5338
一、命令概览
1. 基本语法
PEXPIRE key milliseconds [NX | XX | GT | LT]
参数说明:
| 参数 | 说明 |
|---|---|
key | 要设置过期时间的 Key |
milliseconds | 过期时间,单位为毫秒 |
NX | 仅当 Key 没有过期时间时才设置(Not eXists),Redis 7.0+ |
XX | 仅当 Key 已有过期时间时才设置(eXists),Redis 7.0+ |
GT | 仅当新 TTL 大于当前 TTL 时才设置(Greater Than),Redis 7.0+ |
LT | 仅当新 TTL 小于当前 TTL 时才设置(Less Than),Redis 7.0+ |
返回值:
- 设置成功返回
(integer) 1。 - 设置失败返回
(integer) 0(如 Key 不存在、条件不满足等)。
2. 最简单的示例
先写入一个字符串值,然后设置 5000 毫秒(5 秒)过期:
SET mykey "Hello"
PEXPIRE mykey 5000
返回:
(integer) 1
查看剩余生存时间(毫秒):
PTTL mykey
返回类似:
(integer) 4980
5 秒后 Key 被自动删除:
EXISTS mykey
返回:
(integer) 0
二、PEXPIRE 的基本用法
1. 设置毫秒级过期时间
SET lock:job:1 "owner"
PEXPIRE lock:job:1 300
返回 (integer) 1,表示该锁将在 300 毫秒后过期。
PTTL lock:job:1
返回类似:
(integer) 295
2. 对不存在的 Key 设置过期
PEXPIRE no-such-key 60000
返回:
(integer) 0
Key 不存在时,PEXPIRE 返回 0,不会创建 Key。
3. 毫秒数为 0 的情况
SET temp "value"
PEXPIRE temp 0
EXISTS temp
PEXPIRE temp 0 返回 (integer) 1,Key 立即过期,EXISTS temp 返回 (integer) 0。
4. 负值毫秒数
传入负值时,Key 同样立即过期:
SET temp2 "value"
PEXPIRE temp2 -1
EXISTS temp2
返回 (integer) 0。
5. 毫秒与秒的换算
PEXPIRE key 1500 与 EXPIRE key 1.5 不等价——EXPIRE 只接受整数秒。亚秒级精度必须使用 PEXPIRE:
PEXPIRE mykey 1500
PTTL mykey
返回类似 (integer) 1490,精度达到毫秒级。
如果只需要整秒精度,两种写法等价:
EXPIRE mykey 5
PEXPIRE mykey 5000
6. 覆盖已有过期时间
再次执行 PEXPIRE 会覆盖之前的过期时间(不带条件选项时):
SET mykey "value"
PEXPIRE mykey 60000
PEXPIRE mykey 30000
PTTL mykey
返回类似 (integer) 29995,新设置生效。
三、条件选项(Redis 7.0+)
1. NX — 仅当 Key 没有过期时间时设置
SET mykey "value"
PEXPIRE mykey 60000 NX
PTTL mykey
返回 (integer) 1,TTL 设置成功。
再次用 NX 设置:
PEXPIRE mykey 30000 NX
返回:
(integer) 0
Key 已有过期时间,NX 条件不满足,TTL 保持 60000 毫秒不变。
2. XX — 仅当 Key 已有过期时间时设置
SET fresh:key "value"
PEXPIRE fresh:key 30000 XX
返回:
(integer) 0
Key 没有过期时间,XX 条件不满足。
先设置过期时间再使用 XX:
PEXPIRE fresh:key 60000
PEXPIRE fresh:key 30000 XX
返回 (integer) 1,TTL 被更新为 30000 毫秒。
3. GT — 仅当新 TTL 大于当前 TTL 时设置
SET mykey "value"
PEXPIRE mykey 60000
PEXPIRE mykey 30000 GT
返回 (integer) 0,30000 小于当前 60000,不更新。
PEXPIRE mykey 90000 GT
返回 (integer) 1,90000 大于 60000,TTL 更新为 90000 毫秒。
GT 常用于"只延长、不缩短"的续期场景。
4. LT — 仅当新 TTL 小于当前 TTL 时设置
PEXPIRE mykey 90000
PEXPIRE mykey 120000 LT
返回 (integer) 0,120000 大于当前 90000,不更新。
PEXPIRE mykey 30000 LT
返回 (integer) 1,TTL 缩短为 30000 毫秒。
LT 常用于"只收紧、不放宽"的安全收紧场景。
5. 特殊规则
- 当前 TTL 为
-1(永不过期)时,GT视为无穷大,任何新 TTL 都不满足;LT视为满足,可以设置。 - 用
GT/LT与NX/XX组合使用会报语法错误,只能选择其一。
四、PEXPIRE 与 EXPIRE 的区别
两者唯一的区别是时间单位:
| 特性 | EXPIRE | PEXPIRE |
|---|---|---|
| 时间单位 | 秒 | 毫秒 |
| 最小精度 | 1 秒 | 1 毫秒 |
| 参数类型 | 整数秒 | 整数毫秒 |
| 查询命令 | TTL | PTTL |
| 绝对时间版本 | EXPIREAT | PEXPIREAT |
| 时间戳查询 | EXPIRETIME | PEXPIRETIME |
| 可用版本 | 1.0 | 2.6 |
Redis 内部以毫秒级绝对时间戳记录过期时刻,因此 PEXPIRE 是最直接匹配内部精度的设置方式,没有秒到毫秒的转换误差:
EXPIRE mykey 1
PTTL mykey
返回可能是 999 或 1000,取决于执行时刻的毫秒对齐;而 PEXPIRE mykey 1000 精确对应 1000 毫秒。
选择建议:
- 需要 1 秒以上粒度(会话、缓存):两者均可,
EXPIRE更直观。 - 需要亚秒精度(锁、限流、测试):必须使用
PEXPIRE。 - 查看剩余时间时保持单位一致:用
PEXPIRE设置就用PTTL查询。
五、与相关命令的区别
1. PEXPIRE 与 PEXPIREAT
PEXPIRE 设置相对时间(从现在起多少毫秒),PEXPIREAT 设置绝对时间戳(Unix 毫秒时间戳):
SET mykey "value"
PEXPIREAT mykey 1767225600000
PEXPIRETIME mykey
返回 (integer) 1767225600000。PEXPIREAT 适合"在某个固定时刻过期"(如整点、活动截止时间),不受执行时间影响。
2. PEXPIRE 与 SET … PX
SET key value PX milliseconds 在写入值的同时原子地设置毫秒级过期时间,一条命令完成,不存在竞态:
SET session:1001 "data" PX 1800000
分两步则存在竞态:
SET session:1001 "data" # 与下一条命令之间崩溃,Key 永不过期
PEXPIRE session:1001 1800000
写入新值并设置 TTL 的场景应优先使用 SET ... PX;PEXPIRE 适合为已存在的 Key 补充或修改过期时间。
3. PEXPIRE 与 PERSIST
PERSIST 移除过期时间,PEXPIRE 设置过期时间,两者互逆:
SET mykey "value"
PEXPIRE mykey 60000
PERSIST mykey
PTTL mykey
PERSIST 返回 (integer) 1 后,PTTL 返回 (integer) -1(永不过期)。
4. PEXPIRE 与 PTTL / PEXPIRETIME
PTTL 查询剩余毫秒数,PEXPIRETIME 查询过期时刻的毫秒时间戳:
PEXPIRE mykey 90000
PTTL mykey
PEXPIRETIME mykey
PTTL 返回类似 (integer) 89950;PEXPIRETIME 返回类似 (integer) 1767225615000。两者换算关系:PEXPIRETIME ≈ 当前时间 + PTTL。
5. PEXPIRE 与 GETEX … PX
GETEX key PX milliseconds(Redis 7.0+)在读取 String 值的同时修改过期时间:
SET mykey "value"
GETEX mykey PX 60000
返回 "value",同时 TTL 被设置为 60000 毫秒。PEXPIRE 不读取值,且适用于所有数据类型。
六、过期时间刷新与更新规则
1. 各命令对 TTL 的影响
| 命令 | 对 TTL 的影响 |
|---|---|
PEXPIRE key ms | 设置/覆盖为新的 TTL |
EXPIRE key s | 设置/覆盖为新的 TTL(秒) |
SET key value | 清除 TTL,Key 永不过期 |
SET key value KEEPTTL | 保留 TTL(Redis 6.0+) |
SET key value PX ms | 设置新 TTL |
PERSIST key | 移除 TTL |
RENAME key newkey | TTL 跟随 Key 迁移 |
GETSET key value | 清除 TTL |
HSET、LPUSH 等集合类写命令 | 保留 TTL |
2. 覆盖式更新
不带条件选项的 PEXPIRE 总是覆盖当前 TTL,无论是延长还是缩短:
SET mykey "value"
PEXPIRE mykey 60000
PEXPIRE mykey 1000
PTTL mykey
返回小于等于 1000,TTL 被缩短为 1 秒。
3. 集合写命令不刷新 TTL
对 Hash、List 等类型的修改不会重置过期时间:
HSET user:1001 name "Alice"
PEXPIRE user:1001 60000
HSET user:1001 age 30
PTTL user:1001
TTL 继续倒计时,HSET 不影响过期时间。
七、过期机制原理
1. 惰性过期
访问 Key 时检查是否过期,过期则删除并当作不存在处理。这意味着过期 Key 在被访问或被后台清理之前,可能仍短暂占用内存。
2. 定期过期
Redis 后台周期性随机抽样检查带 TTL 的 Key,删除其中已过期的,避免大量过期 Key 堆积。抽样频率由 hz 配置控制。
3. 内存淘汰
过期删除与内存淘汰是两个机制。内存达到 maxmemory 时,按 maxmemory-policy 淘汰 Key;volatile-* 策略只淘汰带 TTL 的 Key。
4. 主从复制与持久化
PEXPIRE在主节点执行后传播到从节点,从节点不能自主决定过期时刻,过期删除以主节点为准。- AOF 以毫秒时间戳记录过期事件;RDB 快照保存绝对过期时间,加载时已过期的 Key 会被丢弃。
- 源端与目标端系统时间差异会影响
MIGRATE迁移后过期 Key 的实际行为,生产环境应保证 NTP 时间同步。
八、不同数据类型
PEXPIRE 是 Key 级别的通用操作,适用于所有数据类型:
SET str:key "hello"
PEXPIRE str:key 5000
HSET hash:key name "Alice" age 30
PEXPIRE hash:key 5000
RPUSH list:key a b c
PEXPIRE list:key 5000
SADD set:key m1 m2
PEXPIRE set:key 5000
ZADD zset:key 100 "player1"
PEXPIRE zset:key 5000
XADD stream:key * type "login"
PEXPIRE stream:key 5000
每个 PEXPIRE 都返回 (integer) 1。过期时间属于整个 Key,Redis 不支持对 Hash 的字段、List 的元素等子结构单独设置过期时间。
九、事务与并发
1. 在事务中使用
MULTI
PEXPIRE session:1001 1800000
PEXPIRE cache:item 60000
EXEC
EXEC 返回两条命令各自的结果。注意 Redis 事务不回滚,若第一条成功第二条失败(如 Key 不存在返回 0),第一条的设置仍然生效。
2. 设置与读取的竞态
"先检查再设置"不是原子的:
PTTL mykey # 返回 3000
PEXPIRE mykey 60000 # 两条命令之间 Key 可能已被其他客户端删除或设置
对结果敏感的场景应检查 PEXPIRE 返回值,或使用 Lua 脚本:
EVAL "if redis.call('EXISTS', KEYS[1]) == 1 then return redis.call('PEXPIRE', KEYS[1], ARGV[1]) else return 0 end" 1 mykey 60000
3. 分布式锁续期
持锁任务执行时间不确定时,常见模式是后台线程周期性续期:
PEXPIRE lock:job:1 30000 XX
使用 XX 确保仅在锁仍持有时刷新,且周期必须远小于 TTL(如 TTL 30 秒、每 10 秒续期一次),避免续期间隙锁过期。更完善的方案是只允许锁持有者续期,可在 Lua 脚本中校验锁的值。
4. 写入与设置 TTL 的原子性
新建 Key 时分开执行 SET 和 PEXPIRE 存在窗口,应使用原子命令:
# 推荐:一条命令完成
SET lock:job:1 "owner" NX PX 30000
# 不推荐:两步之间存在竞态
SET lock:job:1 "owner"
PEXPIRE lock:job:1 30000
5. Pipeline 批量设置
对大量 Key 设置相同的毫秒级 TTL 时可用 Pipeline 减少网络往返,每个命令有独立返回值:
PEXPIRE key:1 60000
PEXPIRE key:2 60000
PEXPIRE key:3 60000
注意批量设置可能导致 Key 在同一毫秒集中过期,引发"缓存雪崩",建议对 TTL 添加随机抖动。
十、在常见客户端中的使用方式
1. redis-cli
redis-cli PEXPIRE mykey 5000
redis-cli PTTL mykey
redis-cli PEXPIRE lock:job:1 30000 XX
2. Python(redis-py)
import redis
client = redis.Redis(host="localhost", port=6379, decode_responses=True)
client.set("mykey", "Hello")
result = client.pexpire("mykey", 5000)
print(result) # True
print(client.pttl("mykey")) # 例如 4985
# Redis 7.0+ 条件选项通过参数传递
result = client.pexpire("mykey", 30000, xx=True)
print(result) # True 或 False
3. Node.js(node-redis)
import { createClient } from "redis";
const client = createClient();
await client.connect();
await client.set("mykey", "Hello");
const result = await client.pExpire("mykey", 5000);
console.log(result); // true
console.log(await client.pTTL("mykey")); // 例如 4985
await client.quit();
4. Java(Jedis)
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;
try (Jedis jedis = new Jedis("localhost", 6379)) {
jedis.set("mykey", "Hello");
long result = jedis.pexpire("mykey", 5000);
System.out.println(result); // 1
System.out.println(jedis.pttl("mykey")); // 例如 4985
// 新建锁时推荐原子命令
String lockResult = jedis.set(
"lock:job:1", "owner",
SetParams.setParams().nx().px(30000)
);
System.out.println(lockResult); // OK
}
十一、典型业务场景
1. 短时分布式锁
任务执行仅需数百毫秒时,用 PEXPIRE 设置细粒度锁超时,减少锁被抢占后其他客户端的等待:
SET lock:short-task "owner" NX PX 500
2. 精确限流
滑动窗口限流器中,计数 Key 需要精确的窗口毫秒数:
INCR ratelimit:user:1001:1000
PEXPIRE ratelimit:user:1001:1000 60000 NX
首次创建计数时设置 60 秒窗口,NX 保证窗口不被后续请求重置。
3. 高频缓存
TTL 在 1 秒以内的热点缓存(如排行榜快照、股票行情):
SET quote:AAPL "189.50" PX 800
4. 验证码与一次性令牌
短信验证码通常 5 分钟有效,毫秒级命令便于统一用毫秒管理:
SET sms:code:13800001111 "582714" PX 300000
5. 毫秒级倒计时
秒杀活动的精确开始倒计时、考试倒计时等:
SET exam:countdown "started" PX 7200000
6. 幂等性控制
防止重复请求的幂等 Key 设置短毫秒 TTL:
SET idempotency:req-abc "processed" NX PX 10000
十二、性能与使用建议
- 时间复杂度:O(1),
PEXPIRE是@keyspace、@write、@fast类别的快速命令。 - 新建 Key 并设置 TTL 优先使用
SET key value PX ms(需要互斥时加NX),避免两步竞态。 - 已存在 Key 修改 TTL 用
PEXPIRE;只延长不缩短用PEXPIRE key ms GT,只缩短不延长用LT。 - 续期场景使用
XX选项可避免误为已删除的 Key 重新设置 TTL。 - 毫秒级 TTL 意味着 Key 过期更快,大量短 TTL Key 会提高过期删除频率;过期 Key 依赖惰性 + 定期两种机制清理,高峰期可能出现短暂内存滞后。
- 批量设置相同 TTL 会造成同时过期(缓存雪崩),建议加入随机抖动:
PEXPIRE key:1 61340
PEXPIRE key:2 58720
PEXPIRE key:3 62980
PEXPIRE是写命令,会写入 AOF 并复制到从节点,频繁续期(如每秒一次的锁续期)会增加复制流量。- 查询剩余时间用
PTTL保持单位一致;TTL返回的秒数对短 TTL Key 可能直接是0或1,失去毫秒信息。 - ACL 中
PEXPIRE属于@keyspace、@write类别,授权时与EXPIRE、PERSIST等一并考虑。
十三、常见问题排查
问题 1:PEXPIRE 返回 0
按以下顺序检查:
- Key 是否存在:
EXISTS mykey
- 条件选项是否满足(
NX需 Key 无 TTL,XX需有 TTL,GT/LT需比较通过):
PTTL mykey
| PTTL 返回值 | 含义 | NX 结果 | XX 结果 |
|---|---|---|---|
| 正数 | 带 TTL | 0 | 可能 1 |
-1 | 无 TTL | 可能 1 | 0 |
-2 | Key 不存在 | 0 | 0 |
- Redis 版本是否支持条件选项(7.0+),低版本会报语法错误。
问题 2:设置的毫秒数与 PTTL 不一致
PTTL 返回的是查询那一刻的剩余时间,命令执行、网络往返都会消耗几毫秒到几十毫秒,返回值略小于设置值属正常现象。偏差过大时检查:
- 是否有其他客户端覆盖了 TTL。
- 是否执行了
SET(会清除 TTL 后又重新设置)。
问题 3:PTTL 返回 0 但 Key 仍存在
TTL 剩余不足 1 秒时,TTL 返回 0,PTTL 仍返回精确毫秒数。这是 TTL 秒级取整的表现,不是异常:
TTL mykey # (integer) 0
PTTL mykey # (integer) 420
问题 4:锁的续期失效
排查要点:
- 续期间隔是否小于 TTL(建议 TTL 的 1/3)。
- 是否有客户端执行了
SET覆盖锁并清除了 TTL。 - 是否使用了
XX选项但锁已被删除(返回0是正常保护)。 - 网络延迟是否导致续期请求晚于过期时刻。
问题 5:条件选项报语法错误
NX、XX、GT、LT 需要 Redis 7.0+,且四者互斥不能组合。检查版本:
INFO server
低版本可用 Lua 脚本实现类似的条件判断。
问题 6:集群模式下使用
PEXPIRE 按 Key 所在槽路由,正常可用。多 Key 的 Lua 脚本要求所有 Key 在同一槽,跨槽操作会报 CROSSSLOT 错误,需使用 hash tag 保证同槽。
十四、完整练习
下面的示例覆盖基本设置、条件选项、覆盖更新和数据类型验证:
FLUSHDB
# 1. 基本设置与查询
SET demo:temp "data"
PEXPIRE demo:temp 5000
PTTL demo:temp
TTL demo:temp
# 2. 不存在的 Key
PEXPIRE demo:not-exist 60000
# 3. 零值与负值
SET demo:zero "value"
PEXPIRE demo:zero 0
EXISTS demo:zero
SET demo:neg "value"
PEXPIRE demo:neg -100
EXISTS demo:neg
# 4. 覆盖更新
SET demo:cover "value"
PEXPIRE demo:cover 60000
PEXPIRE demo:cover 30000
PTTL demo:cover
# 5. NX 条件
SET demo:nx "value"
PEXPIRE demo:nx 60000 NX
PEXPIRE demo:nx 30000 NX
PTTL demo:nx
# 6. XX 条件
SET demo:xx "value"
PEXPIRE demo:xx 30000 XX
PEXPIRE demo:xx 60000
PEXPIRE demo:xx 30000 XX
PTTL demo:xx
# 7. GT 与 LT 条件
SET demo:cond "value"
PEXPIRE demo:cond 60000
PEXPIRE demo:cond 30000 GT
PEXPIRE demo:cond 90000 GT
PEXPIRE demo:cond 30000 LT
PTTL demo:cond
# 8. 与 PERSIST 的互逆操作
PEXPIRE demo:temp 60000
PERSIST demo:temp
PTTL demo:temp
# 9. 不同数据类型
HSET demo:hash name "Alice" age 30
PEXPIRE demo:hash 60000
PTTL demo:hash
TYPE demo:hash
RPUSH demo:list a b c
PEXPIRE demo:list 60000
PTTL demo:list
# 10. 毫秒精度验证
SET demo:precise "value"
PEXPIRE demo:precise 1500
PTTL demo:precise
TTL demo:precise
预期结果:
demo:temp:PTTL返回略小于5000的值,TTL返回4或5。demo:not-exist:返回(integer) 0。demo:zero、demo:neg:立即过期,EXISTS返回(integer) 0。demo:cover:PTTL略小于30000。demo:nx:第一次NX返回1,第二次返回0,TTL 保持约60000。demo:xx:第一次XX返回0(无 TTL),设置后XX返回1,TTL 更新为约30000。demo:cond:GT 30000返回0,GT 90000返回1,LT 30000返回1,最终PTTL略小于30000。demo:temp:PERSIST后PTTL返回(integer) -1。demo:hash、demo:list:PEXPIRE返回1,类型不变。demo:precise:PTTL返回1400左右,TTL返回0或1。
十五、命令速查表
| 需求 | 命令 |
|---|---|
| 设置过期时间(毫秒) | PEXPIRE key milliseconds |
| 设置过期时间(秒) | EXPIRE key seconds |
| 按毫秒时间戳设置过期 | PEXPIREAT key timestamp |
| 按秒时间戳设置过期 | EXPIREAT key timestamp |
| 写入并设置毫秒 TTL | SET key value PX milliseconds |
| 写入并设置秒 TTL | SET key value EX seconds |
| 写入但保留 TTL | SET key value KEEPTTL |
| 移除过期时间 | PERSIST key |
| 查询剩余时间(毫秒) | PTTL key |
| 查询剩余时间(秒) | TTL key |
| 查询过期时刻(毫秒时间戳) | PEXPIRETIME key |
| 查询过期时刻(秒时间戳) | EXPIRETIME key |
| 读取值并修改 TTL | GETEX key [PX ms] [EX s] [PERSIST] ... |
总结
PEXPIRE 的核心作用是为 Key 设置毫秒级过期时间:
PEXPIRE key milliseconds [NX | XX | GT | LT]
使用时重点注意六点:
PEXPIRE与EXPIRE的唯一区别是时间单位,需要亚秒精度时必须使用PEXPIRE,并用PTTL查询。- 新建 Key 并设置 TTL 优先使用
SET key value PX ms原子命令,避免两步竞态。 - 条件选项
NX/XX/GT/LT(Redis 7.0+)可实现"仅延长"“仅缩短”"仅有 TTL 时设置"等语义,四者互斥。 - 覆盖式更新总是生效;已过期的 Key 无法恢复,
PEXPIRE对不存在的 Key 返回0且不创建 Key。 - 续期场景(分布式锁)建议周期为 TTL 的 1/3 并配合
XX选项。 - 批量设置相同 TTL 会引发缓存雪崩,应添加随机抖动。

380

被折叠的 条评论
为什么被折叠?



