redis 4.0之前del必然卡死主线程,因所有删除均走dbsyncdelete,需主线程逐节点遍历、解引用并调用free()释放内存,无异步路径;unlink则通过delgenericcommand(c,1)触发dbasyncdelete,将内存回收交由bio线程异步执行,主线程仅o(1)摘除字典引用。

DEL在Redis 4.0之前为什么必然卡死主线程
因为没有异步释放路径。Redis 4.0之前所有删除都走dbSyncDelete函数,主线程必须逐个遍历、解引用、调用free()释放内存——尤其是Hash/ZSet这类底层为链表或跳表的结构,几百万节点就得执行几百万次内存回收操作。这不是“慢”,是“停摆”:期间INFO commandstats里del的usec_per_call会飙升到秒级,所有新请求排队等待,超时、502、主从切换都可能触发。
UNLINK和DEL的底层函数差异一目了然
从源码看,两者根本不在一个执行轨道上:
-
DEL→ 调用delCommand→delGenericCommand(c, 0)→ 强制走dbSyncDelete -
UNLINK→ 调用unlinkCommand→delGenericCommand(c, 1)→ 走dbAsyncDelete
关键就在这一个参数1:它让Redis把对象指针塞进后台bio队列,主线程只做字典摘除(O(1)),不碰内存释放逻辑。但注意:UNLINK在4.0之前压根不存在,客户端发过去直接报错ERR unknown command `unlink`。
lazyfree-lazy-user-del配置不是可选项,而是生效开关
UNLINK命令本身只是入口,真正决定它能否异步释放的是这个配置项:
Redis 8.2.3 是一款安全优先的高性能键值存储系统。该版本紧急修复了可能引发远程代码执行(RCE)的高危漏洞(CVE-2025-62507),并解决了 HyperLogLog 及 Cuckoo Filter 等数据结构在特定场景下的崩溃问题。建议所有用户立即升级,以保障生产环境的系统稳定与数据安全。
- 默认值是
no,即UNLINK也会退化成同步删(尤其对大Key) - 必须显式执行
CONFIG SET lazyfree-lazy-user-del yes或写入redis.conf - 验证方式:
CONFIG GET lazyfree-lazy-user-del返回["lazyfree-lazy-user-del","yes"]
没开这个,UNLINK删5GB ZSet照样卡住——你以为的异步,其实还是同步,只是换了个名字。
UNLINK也会悄悄变回DEL的几种真实场景
它不是魔法,是带条件的异步。以下情况会自动降级,且不报错、不提示:
- Key正被
WATCH监控(事务未提交,引用关系未释放) -
OBJECT REFCOUNT key返回值 > 1(比如该Key刚被RENAME过,或存在共享对象) - 后台
bio线程池已满(INFO bio中bio_num_bgrewrite长期为高) - 内存极度紧张,
mem_not_counted_for_lazyfree持续上升
这些场景下UNLINK行为和DEL完全一致,延迟毛刺照旧。线上排查不能只看命令用了没,得盯INFO memory里的lazyfree_pending_objects是否真有增长——没增长,说明它根本没进异步队列。










