Redis命令:UNLINK

UNLINK 用于删除一个或多个 Key,是 DEL 的非阻塞版本。它在主线程中只完成"从键空间解除链接"这一步,实际的内存回收交给后台线程异步执行,因此删除包含大量元素的集合类大 Key 时几乎不会阻塞 Redis。对调用方而言,返回值和语义与 DEL 一致:返回实际删除的 Key 数量。

本文基于 Redis 通用 Key 操作介绍 UNLINK。Redis 命令本身不区分大小写,因此 UNLINKunlinkUnlink 的效果相同;文档统一使用大写形式。UNLINK 自 Redis 4.0 起可用。

资料合集:https://pan.quark.cn/s/10e98d308913https://pan.quark.cn/s/f56bc69c5338

一、命令概览

1. 基本语法

redis 复制代码
UNLINK key [key ...]

参数说明:

参数 说明
key [key ...] 一个或多个要删除的 Key。不存在的 Key 会被忽略,不会报错

返回值:

  • 返回被实际删除(解除链接)的 Key 数量(整数)。
  • 所有指定 Key 都不存在时返回 (integer) 0

2. 最简单的示例

先写入两个字符串值:

redis 复制代码
SET key1 "Hello"
SET key2 "World"

删除一个 Key:

redis 复制代码
UNLINK key1

返回:

text 复制代码
(integer) 1

删除多个 Key(其中 key3 不存在):

redis 复制代码
UNLINK key1 key2 key3

返回:

text 复制代码
(integer) 1

key1 在上一步已被删除,本次只删除了 key2,计数为 1

3. 命令特性

  • 适用于任意数据类型的 Key,包括 String、Hash、List、Set、Sorted Set、Stream。
  • Key 一经 UNLINK,立即对后续命令不可见,无需等待后台内存回收。
  • 不存在的 Key 会被忽略,不产生错误,重复执行是安全的。
  • ACL 类别为 @keyspace@write@fast

1. 删除一个存在的 Key

redis 复制代码
SET greeting "Hello"
UNLINK greeting

返回:

text 复制代码
(integer) 1

删除后 Key 不再存在:

redis 复制代码
EXISTS greeting

返回:

text 复制代码
(integer) 0

2. 删除不存在的 Key

redis 复制代码
UNLINK not-exists

返回:

text 复制代码
(integer) 0

3. 一次删除多个 Key

redis 复制代码
SET a 1
SET b 2
SET c 3
UNLINK a b c

返回:

text 复制代码
(integer) 3

4. 混合存在与不存在的 Key

redis 复制代码
SET exist-1 1
UNLINK exist-1 not-exist-1 not-exist-2

返回:

text 复制代码
(integer) 1

不存在的 Key 不影响计数,也不产生错误。

5. 重复删除

redis 复制代码
SET once 1
UNLINK once
UNLINK once

两次 UNLINK 分别返回:

text 复制代码
(integer) 1
(integer) 0

UNLINK 是幂等且安全的:重复删除同一 Key 不会报错。

6. 已过期的 Key

已经过期的 Key 在 Redis 中被视为不存在,UNLINK 对它返回 0

redis 复制代码
SET short-lived value EX 1

等待超过 1 秒后执行:

redis 复制代码
UNLINK short-lived

返回:

text 复制代码
(integer) 0

三、异步删除的执行原理

1. 两阶段执行

UNLINK 把删除拆成两个阶段:

  1. 主线程解除链接 :从键空间(dict)中移除该 Key 的引用。这一步对每个 Key 是 O(1),耗时与值的大小无关。完成后,Key 立即对 EXISTSGET 等所有命令不可见。
  2. 后台线程回收内存:值对象占用的内存由后台线程异步释放。对于包含大量元素的 Hash、List、Set 等,这部分开销不再占用主线程时间。

因此"删除成功"(返回计数、Key 不可见)与"内存已释放"是两个独立的时刻,后者可能稍晚完成。

2. 小对象的处理

对于元素很少的小对象,启动后台任务的开销可能高于直接释放,Redis 可能仍在当前流程中同步完成释放。是否转入后台由内部阈值决定,属于实现细节,随版本可能调整。从调用方角度看,UNLINK 的设计目标是保证主线程不被大对象的释放过程长时间阻塞。

3. 观察 lazyfree 状态

INFO 输出中的 lazyfree_pending_objects 字段反映当前等待后台线程释放的对象数量。批量删除大 Key 后,该数值可能短暂上升,随后归零:

bash 复制代码
redis-cli INFO | grep lazyfree

4. 与 DEL 的阻塞对比

DEL 是同步删除,必须在主线程中完成全部内存释放。删除一个包含数百万元素的集合时,主线程可能被占用几十到几百毫秒,期间所有其他命令都被延迟;UNLINK 把这部分工作移出主线程,避免此类卡顿。

两者语法和返回值语义完全一致,区别只在删除方式:

对比项 DEL UNLINK
删除方式 同步,主线程释放全部内存 主线程解除链接,后台线程释放内存
大 Key 阻塞风险 高,可能明显阻塞 通常极低
每个命令的开销 O(N),N 为所有被删值包含的元素总数 解除链接 O(1),回收在后台
返回值 实际删除数量 实际删除数量
Key 不可见的时机 命令返回时 命令返回时
ACL 类别 @keyspace @write @slow @keyspace @write @fast
可用版本 1.0+ 4.0+

选择建议:

  • 删除小 Key(普通字符串、小型集合)时两者表现几乎一致,用 DELUNLINK 都可以。
  • 删除大 Key(元素数多的集合、大字符串)或一次删除大量 Key 时,优先使用 UNLINK
  • 代码无法确定 Key 的规模时,默认使用 UNLINK 是更稳妥的选择。

五、与相关命令的区别

见第四章。两者返回计数语义相同,UNLINK 是大 Key 删除场景的推荐命令。

FLUSHDBFLUSHALL 也支持异步方式(Redis 4.0+):

redis 复制代码
FLUSHDB ASYNC
FLUSHALL ASYNC

它们清空的是整个数据库(或所有数据库)的所有 Key,属于高危操作;UNLINK 只删除指定的 Key。整体清库时的异步能力与 UNLINK 同源,都依赖 lazy free 后台线程。

UNLINK 删除整个 Key,以下命令只删除数据结构内部的元素:

  • HDEL key field:删除 Hash 中的字段。
  • SREM key member:删除 Set 中的成员。
  • LREM key count element:删除 List 中的元素。
  • ZREM key member:删除 Sorted Set 中的成员。
  • XDEL key id:删除 Stream 中的消息。

注意:集合内部删除大量元素同样有释放开销。将大集合"瘦身"到空也会删除 Key,但过程可能是同步的;对超大的集合,直接 UNLINK 整个 Key 通常比逐个删除元素高效得多。

GETDEL 读取 String 值的同时同步删除 Key,适用于"取一次即失效"的一次性数据:

redis 复制代码
GETDEL one-time-token

仅删除而不需要返回值时,使用 UNLINK(或 DEL)。

EXPIRE 让 Key 在指定时间后自动过期,是延迟生效的删除;UNLINK 是立即删除:

redis 复制代码
EXPIRE cache-key 300   # 300 秒后自动删除
UNLINK cache-key       # 立即删除

过期删除是否走后台线程由 lazyfree-lazy-expire 配置决定,见下文。

Redis 提供多个配置项控制隐式删除是否走后台线程:

配置项 作用 起始版本
lazyfree-lazy-user-del 设为 yes 后,DEL 表现得像 UNLINK 6.0
lazyfree-lazy-user-flush 设为 yes 后,FLUSHDB/FLUSHALL 默认走后台 6.0
lazyfree-lazy-expire 过期 Key 的删除走后台 4.0
lazyfree-lazy-eviction 内存淘汰删除走后台 4.0
lazyfree-lazy-server-del 隐式删除(如 RENAME 覆盖目标 Key)走后台 4.0

查看与修改:

redis 复制代码
CONFIG GET lazyfree-lazy-user-del
CONFIG SET lazyfree-lazy-user-del yes

修改配置只影响后续操作,不会回溯已执行的动作。

六、事务、Pipeline 与并发场景

1. 在事务中使用

UNLINK 可以放入事务队列:

redis 复制代码
MULTI
UNLINK key-a
UNLINK key-b
EXEC

EXEC 执行后返回各命令的结果,每条 UNLINK 的结果为实际删除数量。

2. 原子性与竞态

UNLINK 是单条命令,服务端一次性完成"解除链接",不存在删除一半的中间状态。但"先读后删"的多步流程不是原子操作。

例如"比较锁值后再释放锁"不能直接写成先 GETUNLINK,否则可能误删其他客户端后来获得的锁。应使用 Lua 脚本将比较与删除作为原子操作:

lua 复制代码
if redis.call("GET", KEYS[1]) == ARGV[1] then
    return redis.call("UNLINK", KEYS[1])
else
    return 0
end

3. Pipeline 与 SCAN 分批删除

需要删除大量 Key 时,先使用 SCAN 增量遍历,再用 Pipeline 批量发送 UNLINK

text 复制代码
UNLINK key:1
UNLINK key:2
UNLINK key:3

不要用 KEYS 先查出所有 Key 再逐个删除,KEYS 本身就会阻塞服务器。每批的数量应控制在合理范围(如几百个),避免单批过大造成请求排队。

4. 持久化与复制

UNLINK 是写命令,与 DEL 一样会持久化到 AOF 并复制到从节点,最终状态与执行 DEL 相同。所有节点(含从节点)都应使用 Redis 4.0 及以上版本。

七、在常见客户端中的使用方式

1. redis-cli

bash 复制代码
redis-cli SET greeting "Hello"
redis-cli UNLINK greeting
redis-cli UNLINK greeting

第二条命令返回 1,第三条返回 0

2. Python(redis-py)

python 复制代码
import redis

client = redis.Redis(host="localhost", port=6379, decode_responses=True)
client.set("greeting", "Hello")
removed = client.unlink("greeting", "not-exists")
print(removed)  # 1

3. Node.js(node-redis)

javascript 复制代码
import { createClient } from "redis";

const client = createClient();
await client.connect();

await client.set("greeting", "Hello");
const removed = await client.unlink(["greeting", "not-exists"]);
console.log(removed); // 1

await client.quit();

4. Java(Jedis)

java 复制代码
import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis("localhost", 6379)) {
    jedis.set("greeting", "Hello");
    long removed = jedis.unlink("greeting", "not-exists");
    System.out.println(removed); // 1
}

不同客户端对返回值的封装可能不同,但都对应实际删除的 Key 数量。旧版客户端 SDK 可能没有封装 unlink 方法,此时可通过 execute_command("UNLINK", ...) 或升级 SDK 解决。

八、典型业务场景

1. 大 Key 清理

删除前先评估规模,再异步删除:

redis 复制代码
MEMORY USAGE big:cache:key
UNLINK big:cache:key

删除包含大量元素的集合 Key 时,UNLINK 能避免主线程被内存释放过程阻塞。

2. 按模式批量删除

清理一批具有相同前缀的临时 Key:

text 复制代码
SCAN 游标 MATCH tmp:batch:2026:* COUNT 500
UNLINK k1 k2 k3 ...(每批数百个)

SCAN 负责渐进式遍历,UNLINK 负责低阻塞删除,两者配合是生产环境批量清理的推荐组合。

3. 缓存主动失效

底层数据更新后删除对应缓存,等待下次访问重建:

redis 复制代码
UNLINK cache:product:1001

缓存 Key 较大或数量较多时,用 UNLINK 替代 DEL 降低对线上请求的影响。

4. 会话与临时数据批量下线

活动结束、会话批量过期前主动清理:

redis 复制代码
UNLINK session:abc session:def session:ghi

九、性能与使用建议

  1. 时间复杂度:每个 Key 的解除链接为 O(1),总命令开销 O(N),N 为 Key 数量;值的内存回收在后台线程完成,复杂度 O(M),M 为值内元素数量,不占用主线程。
  2. 删除大 Key 或批量删除时优先使用 UNLINK,替代 DEL
  3. 批量删除必须配合 SCAN 增量遍历并分批执行,不要使用 KEYS
  4. UNLINKDEL 一样会触发 del 类型的 keyspace 通知,大量删除时注意订阅端的消费能力。
  5. 删除不可逆,执行前确认 Key 名称与影响范围;重要数据先备份(如 COPY)。
  6. 集群模式下,单条 UNLINK 的多个 Key 必须位于同一哈希槽;跨槽位删除需按槽位分组多次执行,可用 hash tag 保证同槽。
  7. 内存释放是异步的:used_memory 可能略滞后于删除动作,进程 RSS 因内存分配器持有内存也可能不会立即下降。
  8. Redis 版本低于 4.0 的实例不支持 UNLINK,调用会返回 unknown command 错误。

十、常见问题排查

按以下顺序检查:

  1. 是否连接到了正确的 Redis 实例、端口和逻辑数据库(SELECT 切换的库会影响命令可见范围)。
  2. Key 是否已经过期(过期 Key 会被视为不存在)。
  3. Key 名称是否包含不可见字符、前后空格或大小写差异。
redis 复制代码
EXISTS key
TTL key
TYPE key
text 复制代码
(error) ERR unknown command 'UNLINK'

实例版本低于 4.0。升级 Redis,或在确认 Key 规模不大的前提下改用 DEL(并可结合 lazyfree-lazy-user-del 配置,前提是版本支持该配置)。

问题 3:删除后内存没有立即下降

两方面的正常现象:

  1. UNLINK 的内存回收是异步的,删除后可观察 lazyfree_pending_objects 是否正在回落。
  2. 即使 used_memory 已下降,进程 RSS 也可能因内存分配器(如 jemalloc)持有内存而不立即归还操作系统。

持续不下降时,再排查是否存在其他大 Key 或内存碎片问题:

redis 复制代码
MEMORY USAGE key
INFO memory

问题 4:删除大 Key 期间仍有延迟抖动

可能原因:

  1. 实际执行的是 DEL 而非 UNLINK(检查代码与 lazyfree-lazy-user-del 配置)。
  2. 单批删除的 Key 数量过大,命令本身和通知产生排队。减小每批数量。
  3. 延迟来自遍历环节(如误用了 KEYS),改用 SCAN
  4. 值属于极小对象,走同步释放路径属于正常实现,通常不构成可感知的阻塞。

问题 5:集群下报 CROSSSLOT 错误

text 复制代码
(error) CROSSSLOT Keys in request don't hash to the same slot

一条 UNLINK 中的多个 Key 分布在不同哈希槽。按槽位分组分别执行,或使用 hash tag(如 user:{1001}:auser:{1001}:b)让相关 Key 落在同一槽。

十一、完整练习

下面的示例覆盖基本删除、不存在的 Key、批量删除、幂等性、集合类型和与 DEL 的对比。请在测试数据库执行;FLUSHDB 会清空当前逻辑数据库中的全部 Key。

redis 复制代码
FLUSHDB

# 1. 基本用法
SET demo:a "1"
UNLINK demo:a
EXISTS demo:a

# 2. 不存在的 Key
UNLINK demo:not-exist

# 3. 批量删除
SET demo:k1 "v1"
SET demo:k2 "v2"
SET demo:k3 "v3"
UNLINK demo:k1 demo:k2 demo:not-exist

# 4. 重复删除(幂等)
SET demo:once "v"
UNLINK demo:once
UNLINK demo:once

# 5. 集合类型
HSET demo:hash f1 v1 f2 v2 f3 v3
UNLINK demo:hash
EXISTS demo:hash
TYPE demo:hash

# 6. 与 DEL 对比(小对象两者表现一致)
SET demo:del-key "v"
DEL demo:del-key
SET demo:unlink-key "v"
UNLINK demo:unlink-key

# 7. 已过期的 Key
SET demo:temp "v" EX 1
TYPE demo:temp
# 等待 1 秒以上后执行:
# UNLINK demo:temp

# 8. 观察后台回收状态
SET demo:big "value"
UNLINK demo:big
INFO lazyfree

# 9. SCAN 配合批量删除
SET demo:s1 "v1"
SET demo:s2 "v2"
SCAN 0 MATCH demo:s* COUNT 100

预期结果:

  • UNLINK demo:a 返回 (integer) 1,随后 EXISTS demo:a 返回 (integer) 0
  • UNLINK demo:not-exist 返回 (integer) 0
  • UNLINK demo:k1 demo:k2 demo:not-exist 返回 (integer) 2
  • 两次 UNLINK demo:once 分别返回 (integer) 1(integer) 0
  • UNLINK demo:hash 返回 (integer) 1EXISTS 返回 0TYPE 返回 none
  • DELUNLINK 对小 Key 均返回 (integer) 1,效果一致。
  • demo:temp 过期后 UNLINK 返回 (integer) 0
  • INFO lazyfree 可查看 lazyfree_pending_objects(通常为 0,批量删除大 Key 后短暂大于 0)。
  • SCAN 返回游标与 demo:s1demo:s2 的 Key 列表,可据此再执行 UNLINK

十二、命令速查表

需求 命令
异步删除一个或多个 Key UNLINK key [key ...]
同步删除一个或多个 Key DEL key [key ...]
读取并删除 String GETDEL key
判断 Key 是否存在 EXISTS key
评估 Key 内存占用 MEMORY USAGE key
增量遍历 Key SCAN cursor [MATCH pattern] [COUNT n]
异步清空当前数据库 FLUSHDB ASYNC
异步清空所有数据库 FLUSHALL ASYNC
设置过期时间(延迟删除) EXPIRE key seconds
让 DEL 默认走异步 CONFIG SET lazyfree-lazy-user-del yes
查看待回收对象数量 INFOlazyfree_pending_objects
复制 Key 作为备份 COPY source destination

总结

UNLINK 的核心作用是按名称非阻塞地删除一个或多个 Key,并返回实际删除数量:

redis 复制代码
UNLINK key [key ...]

使用时重点注意五点:

  • 语义与 DEL 一致:不存在的 Key 被忽略,返回计数只统计实际删除的 Key;重复执行安全幂等。
  • 区别在执行方式:主线程只做 O(1) 的解除链接,内存回收在后台线程完成,Key 在命令返回时即不可见。
  • 删除大 Key、批量清理、缓存主动失效等场景优先使用 UNLINK;小 Key 场景与 DEL 表现几乎一致。
  • 异步释放意味着内存下降可能略滞后,lazyfree_pending_objects 可观察回收状态;RSS 不立即下降属内存分配器正常行为。
  • "先读后删"不是原子操作,锁释放等场景需用 Lua 脚本;集群下单条命令的多个 Key 必须同槽。
相关推荐
Shaoxi Zhang1 小时前
Redis基础——五种核心数据结构(“容器”)
redis
志尊宝6 小时前
Vue3 零基础每日笔记(009):watch 侦听器——数据一变就“做事“
vue.js·笔记·缓存
载数而行5208 小时前
redis集群
redis
FfHUCisI10 小时前
Go 内存分配器概览:从 TCMalloc 到三级缓存架构
缓存·架构·golang
2401_8332693011 小时前
RecyclerView的缓存复用机制
缓存
Escalating_xu11 小时前
【Linux mmap 文件映射】从虚拟地址到页缓存:读写文件、匿名映射与极简 malloc
linux·缓存
云雀衔光12 小时前
多个 MCP Server 怎么编排:数据库 / Redis / Git / 飞书一把梭
java·数据库·人工智能·redis·git·语言模型·飞书
youm200313 小时前
【学习笔记】reids的数据类型
redis·笔记·学习
西瓜拿铁好喝14 小时前
2026 语义缓存实战:把命中契约写进SPEC,MonkeyCode 云端跑通
人工智能·机器学习·缓存