🔍 为什么 DEL 这么慢?
根本原因是 Redis 是单线程模型:
| 痛点 | 解释 |
|---|---|
| DEL 是同步阻塞 | 主线程亲自遍历数据结构、逐块 free() 内存,期间所有其他命令排队等待 |
| 逐个 DEL = 海量网络 RTT | 100M 个 key 就算每个只花 0.1ms,串行下来也是几小时,还反复跨网络往返 |
| 大 Key 放大问题 | DEL 一个百万成员的 ZSet/Hash,时间复杂度 O(N),直接卡住几十~几百毫秒甚至数秒 |
| KEYS 命令不能用 | KEYS * 会一次性遍历全量 keyspace,直接把 Redis 堵死,生产环境禁用 |
✅ 方案一:如果是清空整个 DB → FLUSHDB ASYNC(最快)
bash
# Redis 4.0+ 支持异步 flush
redis-cli FLUSHDB ASYNC
# 或
redis-cli FLUSHALL ASYNC
ASYNC 会把整个 DB 的 keyspace 替换为一个新空表,旧数据扔给后台线程释放。这是亿级 key 清空的最快路径,几乎瞬时返回。
⚠️ 但注意:这会清掉整个库,不能只删其中一部分。
✅✅ 方案二:按 Pattern 删(最常见场景)→ SCAN + UNLINK + 限流(推荐)
这是生产环境标准做法 ,三板斧:SCAN 渐进遍历 + UNLINK 异步删 + sleep 限流。
命令行一键版(最简单实用)
bash
# -i 0.01 每次迭代间 sleep 10ms,保护 Redis 不被打爆
redis-cli --scan --pattern "your:prefix:*" -i 0.01 | \
xargs -L 500 redis-cli UNLINK
-L 500 表示每批最多 500 个 key 打包成一个 UNLINK 调用,减少调用次数又不过度膨胀单条命令。
Python 版(可控性最好,推荐用于 100M 级别)
python
import redis
import time
r = redis.Redis(host="127.0.0.1", port=6379, db=0, decode_responses=True)
def batch_delete_by_pattern(pattern, scan_count=1000, unlink_batch=500, sleep_ms=10):
"""
pattern: 匹配模式 e.g. "temp:*" 或 "user:*:session"
scan_count: SCAN 每次建议遍历量(非精确值,推荐 1000~5000)
unlink_batch: 每批 UNLINK 的 key 数(推荐 200~1000)
sleep_ms: 每批之间的休息微秒数,保护主线程
"""
cursor = 0
total = 0
while True:
cursor, keys = r.scan(cursor=cursor, match=pattern, count=scan_count)
if keys:
# 用 pipeline 批量 unlink,减少 RTT
for i in range(0, len(keys), unlink_batch):
batch = keys[i:i+unlink_batch]
r.execute_command("UNLINK", *batch)
total += len(batch)
if total % 10000 == 0:
print(f" ...已删除 {total} 个 key")
if sleep_ms:
time.sleep(sleep_ms / 1000.0)
if cursor == 0:
break
print(f"✅ 完成,共删除 {total} 个 key")
# 执行
batch_delete_by_pattern("your:prefix:*", scan_count=2000, unlink_batch=500, sleep_ms=10)
关键调参建议:
| 参数 | 起步值 | 调整方向 |
|---|---|---|
scan_count |
2000~5000 | 太大→单次SCAN扫太多;太小→SCAN次数爆炸 |
unlink_batch |
200~500 | 太大→单条UNLINK参数过长;太小→RTT浪费 |
sleep_ms |
5~20ms | 业务高峰期取大值,低谷期取小值 |
✅ 方案三:如果你知道 key 列表 → 分批 UNLINK + Pipeline
如果 key 列表已经在文件里或从别的来源拿到:
bash
# key列表每行一个,分批 UNLINK
cat keys_to_delete.txt | xargs -L 1000 redis-cli UNLINK
或用 Python pipeline 提速:
python
with open("keys.txt") as f:
pipe = r.pipeline()
for i, line in enumerate(f):
pipe.unlink(line.strip())
if i % 1000 == 0:
pipe.execute()
pipe = r.pipeline()
pipe.execute()
⚠️ 如果你是 Redis Cluster → 额外注意 CROSSSLOT
Cluster 模式下,UNLINK k1 k2 k3 要求所有 key 落在同一个 hash slot ,否则报 CROSSSLOT 错误。
解法:按 slot / 按 master 节点分别跑 SCAN,或用 cluster-aware 客户端逐 key unlink:
python
# redis-py cluster 模式:逐 key unlink(避免 CROSSSLOT)
from redis.cluster import RedisCluster
rc = RedisCluster(startup_nodes=[{"host":"127.0.0.1","port":7000}], decode_responses=True)
cursor = 0
while True:
cursor, keys = rc.scan(cursor=cursor, match="temp:*", count=2000)
for k in keys:
rc.unlink(k) # ← 逐 key 就不会 CROSSSLOT
if cursor == 0:
break
📊 各方案对比速查
| 场景 | 推荐方案 | 阻塞风险 | 速度 | 备注 |
|---|---|---|---|---|
| 清整个 DB | FLUSHDB ASYNC |
⭐几乎零 | ⚡最快 | 全清,不可部分筛选 |
| 按 prefix/pattern 删 | SCAN + UNLINK + 限流 |
极低 | 快 | ★生产首选★ |
| 已知 key 列表 | 分批 UNLINK + Pipeline |
低 | 很快 | 注意 cluster 的 slot |
| 有大 Key(百万元素的集合) | 必须 UNLINK,别用 DEL | DEL会卡死 | --- | 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 清理的铁三角。