不能直接用keys+del——因keys是阻塞式全量扫描,会卡住redis单线程,导致请求堆积、超时和告警;生产环境必须用scan迭代+分批del或unlink,配合count参数与客户端节奏控制。

为什么不能直接用 KEYS + DEL 在生产环境批量删 key
因为 KEYS 是阻塞式全量扫描,Redis 单线程下一旦 key 数量大(比如几十万),会卡住所有请求,超时、连接堆积、监控告警全来。线上绝对禁用。替代方案必须是渐进式、非阻塞的,而 Lua 脚本本身并不能绕过这个限制——它只是把操作原子化,但若脚本里写 redis.call('KEYS', pattern),照样阻塞。
正确做法:用 SCAN 迭代 + DEL 分批删
Redis 的 SCAN 是非阻塞游标式遍历,配合 Lua 可封装成安全批量删除逻辑。注意:Lua 脚本中不能直接循环调用 SCAN(因 Redis 不支持 while/cursor 状态保持),所以实际应由客户端驱动迭代,Lua 只负责单次 SCAN + DEL 批处理。
推荐客户端侧实现(以 Python 为例):
import redis r = redis.Redis() cursor = 0 pattern = "user:session:*" count = 100 # 每次 SCAN 最多返回 key 数 <p>while True: cursor, keys = r.scan(cursor=cursor, match=pattern, count=count) if keys: r.delete(*keys) # 批量删,原子性由服务端保证 if cursor == 0: break</p>
关键点:
-
SCAN的count参数只是提示,不保证返回数量,也不代表总耗时可控;真正控制节奏靠客户端 sleep 或限流 - 避免在 Lua 中用
redis.call('DEL', unpack(keys))—— Lua 5.1 不支持unpack(Redis 内置 Lua 版本固定为 5.1),得用table.unpack或循环调用DEL - 如果坚持纯 Lua 方案(如通过
EVAL一次性提交),只能删固定小批次(比如最多 10 个 key),否则可能超时或 OOM
硬上 Lua 批删的最小可行脚本(仅限小规模、低频场景)
以下脚本适用于已知 key 数量少(SCAN 获取一批 key 后立即 DEL,不返回结果,不处理游标续扫:
Redis 8.2.3 是一款安全优先的高性能键值存储系统。该版本紧急修复了可能引发远程代码执行(RCE)的高危漏洞(CVE-2025-62507),并解决了 HyperLogLog 及 Cuckoo Filter 等数据结构在特定场景下的崩溃问题。建议所有用户立即升级,以保障生产环境的系统稳定与数据安全。
local keys = redis.call('SCAN', 0, 'MATCH', ARGV[1], 'COUNT', ARGV[2])
for i, key in ipairs(keys[2]) do
redis.call('DEL', key)
end
return #keys[2]
调用方式:
redis-cli --eval del_by_pattern.lua , "user:log:*" "50"
注意:
-
SCAN返回的是{cursor, {key1,key2,...}},所以取keys[2]才是 key 列表 - 参数用
,分隔,ARGV[1]是 pattern,ARGV[2]是 count - 该脚本无法处理 scan 游标 >0 的情况,只扫一轮;想扫完全部必须客户端循环
误删风险与防护措施
Pattern 匹配容易误伤,比如 user:*:cache 可能匹配到 user:123:cache 和 user:admin:cache:tmp。上线前务必验证:
- 先用
SCAN 0 MATCH "your:pattern" COUNT 1000抽样看实际命中的 key - 禁止在 pattern 中使用
*开头(如*:temp),会导致全库扫描,性能极差 - 生产环境建议加白名单校验:在 Lua 中对每个 key 做前缀判断,例如
if string.match(key, "^user:[%d]+:session$") then ... - Redis 6.0+ 可考虑用
ACL限制账号只有指定 key 前缀的DEL权限,从权限层兜底
真正难的不是写几行 Lua,而是控制扫描节奏、避免抖动、以及确认删的确实是“该删的”。别让自动化变成事故加速器。










