SCAN 配合 redis-cli --pipe 是生产环境最稳妥的批量删前缀 key 方式,不阻塞、可控、无内存爆炸风险;因 KEYS 全库遍历会阻塞主线程、耗尽资源且 Redis 6.0+ 默认禁用,而 SCAN 支持游标分批、流式处理,配合 pipeline 或 --pipe 可安全高效删除。

直接结论:用 SCAN 配合 redis-cli --pipe 是生产环境最稳妥的批量删前缀 key 方式,不阻塞、可控、无内存爆炸风险。
为什么不能直接用 KEYS + DEL?
因为 KEYS 是全库遍历命令,在 key 数量超过几万时就会明显卡住 Redis。它会阻塞主线程,期间所有读写请求排队等待,监控上表现为 latency 突增、connected_clients 堆积。哪怕只执行一次 redis-cli KEYS "user:*" | xargs redis-cli DEL,也等同于把整个键空间拉进客户端内存再发删除指令——网络带宽、客户端内存、Redis CPU 全部承压。
常见错误现象:(error) BUSY Redis is busy running a script. You can only run SCRIPT KILL or SHUTDOWN NOSAVE. 或者客户端超时断连,但 Redis 实际还在苦哈哈扫 key。
-
KEYS时间复杂度是 O(N),且不可中断 - 返回结果无分页、无游标,无法流式处理
- Redis 6.0+ 默认禁用
KEYS(通过rename-command KEYS "")
SCAN + redis-cli --pipe 的实操写法
这是 CLI 层面最轻量、最可靠的做法,无需写脚本、不依赖 Python/Java 环境,适合运维或临时清理。
核心逻辑:用 SCAN 分批捞 key,每批拼成 DEL key1 key2 ... 命令,通过管道一次性发给 Redis 执行。
示例命令(删所有 cache:session: 开头的 key):
redis-cli --scan --pattern "cache:session:*" | xargs -L 1000 redis-cli DEL
说明:
-
--scan --pattern是redis-cli封装好的SCAN迭代,自动处理游标,等价于反复调用SCAN cursor MATCH "cache:session:*" COUNT 1000 -
xargs -L 1000表示每 1000 个 key 合并成一条DEL命令,避免单次命令参数过长或 Redismaxbulklen限制 - 如果 key 名含空格或特殊字符,加
-0(配合--null)更安全,但绝大多数业务 key 不需要
Python 脚本中 SCAN + pipeline 的关键细节
当需要加日志、重试、限速或集成到部署流程时,Python 更灵活,但容易踩坑。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
正确做法是:每次 SCAN 返回一批 key 后,立即用 pipeline 批量 delete,而不是攒满全部再删。
示例片段(关键逻辑):
cursor = 0
while cursor != 0 or cursor == 0: # 初始 cursor=0 是合法起点
cursor, keys = client.scan(cursor=cursor, match="log:*", count=500)
if keys:
pipe = client.pipeline()
for key in keys:
pipe.delete(key)
pipe.execute() # 立即执行,不累积
if cursor == 0:
break容易忽略的点:
-
count不是“保证返回数量”,而是提示值;设为 500~2000 较稳,太小(如 10)会导致 RTT 次数暴增,太大(如 10000)可能单次响应超时 - 必须检查
cursor == 0才退出循环,不能只看keys是否为空——SCAN 可能返回空数组但游标非 0 - 不要在循环外建一个全局
pipeline累积所有 key,内存会随 key 总量线性增长,4000 万 key 直接 OOM
Lua 脚本方案看似原子,其实有硬伤
有人倾向用 EVAL 执行 Lua 脚本实现“查+删”原子性,例如:
local keys = redis.call('SCAN', 0, 'MATCH', ARGV[1], 'COUNT', 1000)
for i, key in ipairs(keys[2]) do
redis.call('DEL', key)
end问题在于:
- SCAN 在 Lua 中不支持游标延续,
redis.call('SCAN', 0, ...)每次都从头扫,重复遍历,效率极低 - Redis 6.0+ 对 Lua 脚本执行时间有硬限制(
lua-time-limit),超时会报ERR Error running script (call to f_...): @user_script:3: user_script:3: too many results to unpack - 无法做进度反馈、失败重试、并发控制,出错就得重跑整轮
真正需要原子性场景极少,而 SCAN + pipeline 已足够满足“不丢不重”的业务诉求。
最易被忽略的一点:SCAN 不保证遍历完整性——若在迭代过程中有 key 被新增或删除,可能漏掉或重复。但对“批量删旧数据”这类操作,这反而是优势:不因中途写入而无限循环,也不强求绝对精确。

















