SCAN比KEYS更安全因其实现增量式迭代、不阻塞主线程,但需客户端处理游标、去重和分页;清理大key须渐进降级结构(如HSCAN+HDEL、LTRIM等),避免直接DEL引发阻塞或内存压力。

SCAN 命令为什么比 KEYS 更安全
因为 KEYS 是全量遍历,会阻塞 Redis 主线程直到扫描完成,而 SCAN 是增量式迭代,每次只返回少量 key,不阻塞服务。但要注意:SCAN 不保证一次遍历完所有 key,也不保证结果不重复,需客户端做去重和分页控制。
常见错误是直接用 SCAN 0 MATCH big:* COUNT 1000 然后一次性收集全部结果再 DEL——这等于把压力从服务端转移到客户端内存,且仍可能触发大量同步删除开销。
-
COUNT只是提示值,实际返回数量可能更少(尤其数据分布稀疏时) - 游标为
0表示开始,返回新游标非0需继续调用,直到游标再次为0 - 匹配模式中通配符不能放在开头(如
*big),否则无法利用内部编码优化,性能更差
如何用 SCAN + DEL 安全清理大 Hash / List / ZSet
单纯 DEL 大 Key 仍会阻塞,尤其当 value 占用数 MB 以上内存时。必须先降级结构再删除:对 HASH 用 HSCAN 分批 HDEL,对 LIST 用 LTRIM 渐进截断,对 ZSET 用 ZREMRANGEBYRANK 或 ZREMRANGEBYSCORE。
例如清理一个超大 HASH:
SCAN 0 MATCH big:hash:* COUNT 500
拿到一批 key 后,对每个 key 执行:
HSCAN <key> 0 COUNT 100
循环获取 field,每次最多删 10–20 个(避免单次 HDEL 过多):
HDEL <key> field1 field2 ...
直到 HLEN <key> == 0,再执行最终 DEL <key>。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 不要用
HEXISTS逐个查 field 是否存在——它本身是 O(1),但网络往返叠加后放大延迟 -
HSCAN的游标也需完整管理,不能假定一次0游标就扫完 - 若大 key 是
LIST且只保留尾部数据,优先用LRANGE+LPUSH构建新 list,再RENAME切换,比反复LTRIM更可控
Python 脚本示例:带休眠的渐进式清理
直接在 redis-cli 里手敲 SCAN/DEL 容易出错,推荐用脚本控制节奏。核心是每次操作后主动 time.sleep(0.01)(10ms),让出 CPU 给主线程处理客户端请求。
关键参数逻辑:
- 每轮最多处理 50 个匹配 key
- 每个 key 内部最多删 20 个 field / element
- 两次操作间隔 ≥ 10ms,可通过环境变量调整
- 遇到
BUSYKEY或OOM command not allowed错误立即暂停 100ms 并重试
示例片段(使用 redis-py):
cursor = 0
while cursor != 0 or cursor == 0 and first_run:
cursor, keys = r.scan(cursor=cursor, match="big:*", count=50)
for key in keys:
# 根据 type 分流处理
t = r.type(key)
if t == b"hash":
hscan_and_hdel(r, key, batch_size=20)
elif t == b"list":
ltrim_progressive(r, key, keep_tail=100)
# ... 其他类型
time.sleep(0.01) # 关键:释放主线程容易被忽略的三个细节
集群环境下,SCAN 只作用于当前 slot 所在节点,不会跨节点自动聚合结果。如果没做 key hash 对齐(比如没加 {...} 标签),大 key 可能散落在多个节点,需分别连接各节点执行清理。
另外两个坑:
- 某些监控工具或 proxy(如 Twemproxy、Codis)不支持
SCAN,会直接报错或静默失败,务必在目标 Redis 实例直连验证 -
DEL操作虽异步,但其内存回收由后台线程完成;若同时大量DEL,可能触发频繁的内存碎片整理(active defrag),反而拖慢响应——所以“慢”不是为了偷懒,是给系统留喘息空间
真正麻烦的从来不是怎么删,而是删完之后发现某个下游服务还缓存着旧 key 的空值,或者 watch dog 误判节点失联。清理前最好先 CLIENT LIST 看看有没有长连接在读这个 key。

















