Redis 大量 Key 删除慢的根因与系统化解决方案
为什么 DEL 这么慢根本原因是Redis 是单线程模型痛点解释DEL 是同步阻塞主线程亲自遍历数据结构、逐块free()内存期间所有其他命令排队等待逐个 DEL 海量网络 RTT100M 个 key 就算每个只花 0.1ms串行下来也是几小时还反复跨网络往返大 Key 放大问题DEL 一个百万成员的 ZSet/Hash时间复杂度 O(N)直接卡住几十~几百毫秒甚至数秒KEYS 命令不能用KEYS *会一次性遍历全量 keyspace直接把 Redis 堵死生产环境禁用✅ 方案一如果是清空整个 DB →FLUSHDB ASYNC最快# Redis 4.0 支持异步 flushredis-cli FLUSHDB ASYNC# 或redis-cli FLUSHALL ASYNCASYNC会把整个 DB 的 keyspace 替换为一个新空表旧数据扔给后台线程释放。这是亿级 key 清空的最快路径几乎瞬时返回。⚠️ 但注意这会清掉整个库不能只删其中一部分。✅✅ 方案二按 Pattern 删最常见场景→SCAN UNLINK 限流推荐这是生产环境标准做法三板斧SCAN 渐进遍历 UNLINK 异步删 sleep 限流。命令行一键版最简单实用# -i 0.01 每次迭代间 sleep 10ms保护 Redis 不被打爆redis-cli--scan--patternyour:prefix:*-i0.01|\xargs-L500redis-cli UNLINK-L 500表示每批最多 500 个 key 打包成一个 UNLINK 调用减少调用次数又不过度膨胀单条命令。Python 版可控性最好推荐用于 100M 级别importredisimporttime rredis.Redis(host127.0.0.1,port6379,db0,decode_responsesTrue)defbatch_delete_by_pattern(pattern,scan_count1000,unlink_batch500,sleep_ms10): pattern: 匹配模式 e.g. temp:* 或 user:*:session scan_count: SCAN 每次建议遍历量非精确值推荐 1000~5000 unlink_batch: 每批 UNLINK 的 key 数推荐 200~1000 sleep_ms: 每批之间的休息微秒数保护主线程 cursor0total0whileTrue:cursor,keysr.scan(cursorcursor,matchpattern,countscan_count)ifkeys:# 用 pipeline 批量 unlink减少 RTTforiinrange(0,len(keys),unlink_batch):batchkeys[i:iunlink_batch]r.execute_command(UNLINK,*batch)totallen(batch)iftotal%100000:print(f ...已删除{total}个 key)ifsleep_ms:time.sleep(sleep_ms/1000.0)ifcursor0:breakprint(f✅ 完成共删除{total}个 key)# 执行batch_delete_by_pattern(your:prefix:*,scan_count2000,unlink_batch500,sleep_ms10)关键调参建议参数起步值调整方向scan_count2000~5000太大→单次SCAN扫太多太小→SCAN次数爆炸unlink_batch200~500太大→单条UNLINK参数过长太小→RTT浪费sleep_ms5~20ms业务高峰期取大值低谷期取小值✅ 方案三如果你知道 key 列表 → 分批 UNLINK Pipeline如果 key 列表已经在文件里或从别的来源拿到# key列表每行一个分批 UNLINKcatkeys_to_delete.txt|xargs-L1000redis-cli UNLINK或用 Python pipeline 提速withopen(keys.txt)asf:piper.pipeline()fori,lineinenumerate(f):pipe.unlink(line.strip())ifi%10000:pipe.execute()piper.pipeline()pipe.execute()⚠️ 如果你是 Redis Cluster → 额外注意 CROSSSLOTCluster 模式下UNLINK k1 k2 k3要求所有 key 落在同一个 hash slot否则报CROSSSLOT错误。解法按 slot / 按 master 节点分别跑 SCAN或用 cluster-aware 客户端逐 key unlink# redis-py cluster 模式逐 key unlink避免 CROSSSLOTfromredis.clusterimportRedisCluster rcRedisCluster(startup_nodes[{host:127.0.0.1,port:7000}],decode_responsesTrue)cursor0whileTrue:cursor,keysrc.scan(cursorcursor,matchtemp:*,count2000)forkinkeys:rc.unlink(k)# ← 逐 key 就不会 CROSSSLOTifcursor0:break 各方案对比速查场景推荐方案阻塞风险速度备注清整个 DBFLUSHDB ASYNC⭐几乎零⚡最快全清不可部分筛选按 prefix/pattern 删SCAN UNLINK 限流极低快★生产首选★已知 key 列表分批UNLINK Pipeline低很快注意 cluster 的 slot有大 Key百万元素的集合必须UNLINK别用 DELDEL会卡死—DEL 同步释放内存堵主线程临时救急/测试KEYS DEL不推荐生产高—KEYS 全量遍历阻塞️ 长期架构预防治本100M 个 key 本身就是一种设计债务建议顺手做给临时数据加 TTL— 让 Redis 自己惰性过期根本不用手动删Key 命名加前缀隔离—temp:*/cache:*/session:*分开脏数据不跟核心数据混一个 DB超大 Value 拆小— 一个 Hash 放百万元素不如按hash:{shard_id}拆 100 个小 Hash定期MEMORY PURGE— 删完后释放页碎片Redis 4.0一句话总结DEL → 换成 UNLINK遍历 → 用 SCAN 别用 KEYS操作 → 分批 sleep 限流。这是 Redis 亿级 key 清理的铁三角。