SCAN不能在Lua脚本中直接调用,因其未被列入Redis Lua环境白名单命令,必须由客户端分批SCAN后传入匹配key列表,再通过原子Lua脚本批量DEL。

SCAN 命令为什么不能直接在 Lua 脚本里用
因为 SCAN 是 Redis 的客户端命令,不是服务端可执行的 Lua 内置函数。你在 Lua 脚本里写 redis.call('SCAN', ...) 会报错:ERR unknown command 'SCAN' —— Redis 的 Lua 环境只允许调用少数白名单命令(DEL、EXISTS、GET 等),SCAN 不在其中。
所以必须由客户端驱动 SCAN 过程,每次拿到一批 key 后,再批量传给 Lua 脚本处理。
如何安全高效地批量删除前缀匹配的 key
核心思路是:客户端用 SCAN 游标分批拉取 key,过滤出匹配前缀的子集,再通过一个原子 Lua 脚本批量 DEL。这样既避免了 KEYS 阻塞,又保证了每批删除的原子性。
推荐的 Lua 脚本如下(注意:它不接收 pattern,只接收明确的 key 列表):
for i, key in ipairs(KEYS) do
redis.call('DEL', key)
end
return #KEYS客户端调用时需确保传入的 key 列表非空,且每个 key 都已通过前缀判断(比如用 Lua string.match 或 Python 的 .startswith())。
- 每次
SCAN建议用COUNT 100,太大易超时,太小请求频繁 - 务必检查游标返回值:当游标变回
"0"时才算遍历完成 - 不要在脚本里做
KEYS或正则扫描——这会拖垮 Redis - 生产环境建议加
sleep(0.01)在每次脚本调用后,减轻主节点压力
Python 示例:用 redis-py 实现带前缀的渐进式清理
以下逻辑能跑通,但要注意异常处理和重试:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
import redis
<p>r = redis.Redis()
cursor = 0
prefix = "session:"
deleted_total = 0</p><p>while True:
cursor, keys = r.scan(cursor=cursor, match=prefix + "*", count=100)</p><h1>只保留真正匹配前缀的 key(SCAN 的 match 是 glob,不是正则,但这里够用)</h1><pre class='brush:php;toolbar:false;'>filtered_keys = [k for k in keys if k.startswith(prefix.encode())]
if filtered_keys:
# 调用 Lua 脚本
script = r.register_script("for i, key in ipairs(KEYS) do redis.call('DEL', key) end return #KEYS")
deleted = script(keys=filtered_keys)
deleted_total += deleted
if cursor == 0:
breakprint(f"Deleted {deleted_total} keys")
注意点:
-
match=prefix + "*"中的*是 glob 通配符,不是正则;Redis 不支持正则匹配 key - 如果 key 是二进制或含特殊字符,
startswith()判断要用bytes类型比较 - 脚本注册只需一次,别放在循环里重复注册
- 若遇到
redis.exceptions.BusyScriptError,说明脚本执行超时(默认 5 秒),得优化脚本或减小单批 key 数量
为什么不用 EVAL 直接嵌入匹配逻辑
有人想把前缀判断也写进 Lua 脚本里,比如传入 prefix 和 COUNT,再让脚本内部 SCAN——这不可行。原因还是那个:SCAN 命令根本不能在 Lua 中调用。
另一个常见错误是传入 match 模式让脚本去遍历所有 key,然后用 redis.call('KEYS', ...) ——这更危险:KEYS 是阻塞命令,数据量大时直接卡死 Redis。
真正的边界在于:Lua 脚本只能操作它被明确传入的 KEYS 或 ARGV,不能“发现”新 key。所有发现逻辑必须由客户端承担,且必须用非阻塞方式(即 SCAN + 游标)。
这个限制常被忽略,结果就是脚本看似简洁,实则要么报错,要么在线上引发雪崩。

















