Redis LREM 命令详细教程
LREM移除列表中与指定元素相等的前 count 个元素,是按值删除列表元素的基础命令。count 同时决定删除数量与扫描方向:可以从表头向表尾删、从表尾向表头删,也可以一次性删完全部匹配。它从 Redis 1.0.0 起提供,常用于清理队列中的无效任务或按值维护列表内容。
资料合集:https://pan.quark.cn/s/10e98d308913、https://pan.quark.cn/s/f56bc69c5338
一、概览与语法
LREM key count element| 项目 | 说明 |
|---|---|
| 数据类型 | List |
| 支持版本 | Redis 1.0.0 起 |
| key | 目标列表 Key |
| count | 移除数量与方向:大于 0 从表头向表尾移除 count 个;小于 0 从表尾向表头移除 count 的绝对值个;等于 0 移除全部匹配元素 |
| element | 要移除的目标值,按字节精确匹配 |
| 返回值 | 整数:本次被移除的元素个数 |
| 时间复杂度 | O(N+M),N 为列表长度,M 为被移除的元素数 |
| ACL | @write、@list、@slow |
| 命令标记 | write |
官方说明:从存储于 key 的列表中移除前 count 个与 element 相等的元素。count 大于 0 时从表头向表尾移除;count 小于 0 时从表尾向表头移除;count 等于 0 时移除所有匹配元素。$TRAE_REF
二、基础示例
以下命令在测试实例的 redis-cli 中执行。文中结果是预期说明,未实际连接 Redis 运行。示例沿用官方文档的数据与结果。
DEL tutorial:{lrem}:mylist RPUSH tutorial:{lrem}:mylist "hello" RPUSH tutorial:{lrem}:mylist "hello" RPUSH tutorial:{lrem}:mylist "foo" RPUSH tutorial:{lrem}:mylist "hello" LREM tutorial:{lrem}:mylist -2 "hello" LRANGE tutorial:{lrem}:mylist 0 -1预期结果:列表初始为"hello"、"hello"、"foo"、"hello"共 4 个元素;LREM的 count 为 -2,表示从表尾向表头移除 2 个"hello",返回2;最后LRANGE返回"hello"、"foo",被移除的是末尾两个 hello,表头 hello 与中间的 foo 保留。
三、count 参数的三种语义
count 的正负与零决定删除方向和数量,用同一个列表分别验证:
DEL tutorial:{lrem}:ex RPUSH tutorial:{lrem}:ex a b a a LREM tutorial:{lrem}:ex 2 a LRANGE tutorial:{lrem}:ex 0 -1 DEL tutorial:{lrem}:ex RPUSH tutorial:{lrem}:ex a b a a LREM tutorial:{lrem}:ex -2 a LRANGE tutorial:{lrem}:ex 0 -1 DEL tutorial:{lrem}:ex RPUSH tutorial:{lrem}:ex a b a a LREM tutorial:{lrem}:ex 0 a EXISTS tutorial:{lrem}:ex预期结果:列表初始为a、b、a、a。count 为 2 时从表头扫描,移除索引 0 和 2 的两个 a,返回2,剩余b、a;count 为 -2 时从表尾扫描,移除索引 3 和 2 的两个 a,返回2,剩余a、b;count 为 0 时移除全部 3 个 a,返回3,列表被清空且 Key 随之删除,EXISTS返回0。
四、返回值与空结果
LREM返回整数:被移除的元素个数。返回 0 有两种不可区分的情形——Key 不存在,或元素在列表中没有出现;两种情况都不报错。
| 情形 | 返回 |
|---|---|
| 移除了 N 个匹配元素 | N |
| Key 不存在 | 0 |
| 元素在列表中未出现 | 0 |
如果业务需要区分“列表不存在”与“没匹配到元素”,应额外执行 EXISTS 或 TYPE 判断,不能只看 LREM 的返回值。
EXISTS tutorial:{lrem}:ex LREM tutorial:{lrem}:ex 1 a LREM tutorial:{lrem}:mylist 5 no-such预期结果:上一节 count 为 0 的示例已把 ex 清空并删除 Key,EXISTS返回0;对不存在的 Key 执行LREM返回0;对存在的 mylist 移除未出现的元素同样返回0,三种结果无法从返回值区分。
五、精确匹配语义
- 字节级比较:
LREM按字节内容逐一比较元素,"A"与"a"是不同的值。 - 不支持通配符:element 是普通字符串,
"a*"只匹配字面的a*,不会匹配abc。 - 空字符串可匹配:element 为空字符串是合法的,可以移除值为空串的元素。
RPUSH tutorial:{lrem}:ex "a" "A" "a*" LREM tutorial:{lrem}:ex 0 a LRANGE tutorial:{lrem}:ex 0 -1预期结果:LREM返回1,只移除了小写的a;LRANGE返回"A"、"a*",大写 A 与带星号的 a* 均未被误删。
六、边界情况与错误处理
| 场景 | 行为 |
|---|---|
| Key 不存在 | 返回 0,不报错 |
| 元素在列表中不存在 | 返回 0,不报错 |
| Key 类型不是 List | 报 WRONGTYPE |
| count 缺失或不是整数 | 报语法错误或参数类型错误 |
| element 为空字符串 | 允许,按空串匹配 |
| count 为 0 | 移除全部匹配元素 |
SET tutorial:{lrem}:str text LREM tutorial:{lrem}:str 0 a预期结果:对 String 类型执行报 WRONGTYPE,类型检查先于匹配扫描。
七、与相近命令的区别
| 命令 | 定位方式 | 是否修改列表 | 说明 |
|---|---|---|---|
| LREM | 按值匹配删除 | 是(变短) | 按值删除前 count 个匹配元素 |
| LPOS | 按值查找返回索引 | 否 | 只定位不删除,常与 LREM/LSET 组合 |
| LTRIM | 按索引区间保留 | 是(变短) | 区间语义同 LRANGE,与值无关 |
| LPOP / RPOP | 按位置弹出 | 是(变短) | 只删表头或表尾一个元素,不看值 |
| DEL / UNLINK | 删除整个 Key | 是 | 连 Key 带数据整体删除 |
选择思路:知道值用LREM,知道位置用LPOP/RPOP或LTRIM,只想查找用LPOS,整键放弃用DEL/UNLINK。
八、客户端示例
前提为已安装 redis-py 并准备好本地测试实例。redis-py 4.0 及以上版本的签名为lrem(name, count, value),旧版为lrem(name, value, num),升级时注意参数顺序。返回值为 int,表示移除个数。
importredis r=redis.Redis(host="localhost",port=6379,decode_responses=True)key="tutorial:{lrem}:py"try:r.delete(key)r.rpush(key,"a","b","a","a")print(r.lrem(key,2,"a"))# 2,从表头移除两个 aprint(r.lrange(key,0,-1))# ['b', 'a']print(r.lrem(key,0,"a"))# 1,移除剩余的全部 a,Key 随之删除print(r.lrem(key,1,"a"))# 0,Key 已不存在print(r.lrem("no-such",1,"a"))# 0,Key 不存在也不报错finally:r.delete(key)r.close()Java(Jedis)示例,lrem返回long:
try(Jedisjedis=newJedis("localhost",6379)){Stringkey="tutorial:{lrem}:java";jedis.del(key);jedis.rpush(key,"a","b","a","a");System.out.println(jedis.lrem(key,-2,"a"));// 2,从表尾移除两个 aSystem.out.println(jedis.lrange(key,0,-1));// [a, b]System.out.println(jedis.lrem(key,0,"a"));// 1,删除最后一个 ajedis.del(key);}九、原子性与性能
复杂度:O(N+M),N 为列表长度,M 为被移除的元素数。即使只删 1 个元素,最坏也要扫过整个列表定位它,因此LREM在大列表上属于潜在慢命令。
慢命令风险:对十万级以上元素的列表执行LREM key 0 x会完整扫描并可能大规模改写节点,阻塞事件循环。高频按值删除的场景应考虑改用 Hash、Set 或有序集合。
原子性:单命令原子,并发客户端无需加锁;在 MULTI/EXEC 与 Lua 脚本中可将多次 LREM 与其他命令组合成整体事务。
读改写组合非原子:先 LPOS 定位再 LREM 的两步操作之间可能被其他客户端插入,需要严格一致时用 WATCH 或 Lua 脚本包裹。
十、典型业务场景
队列清理:延迟队列中任务已失败或被取消,用LREM key 1 task-id按值移除该任务。
按值去重:列表被写入方重复投递后,用LREM key 0 value清掉某值的全部副本。
白名单维护:以 List 存放 IP 或用户白名单,退订时LREM key 1 user-id摘除。
先定位再删除:先用LPOS key element确认值的位置与份数,再决定 count 用 1 还是 0,避免误删。
十一、练习、排错与总结
练习:新建tutorial:{lrem}:ex,用 RPUSH 写入x y x z x;执行LREM ex 1 x确认返回1且剩余y x z x(只删表头的 x);再执行LREM ex -2 x返回2且剩余y z;重建列表后执行LREM ex 0 x返回3且EXISTS为0;对不存在的 Key 执行LREM返回0不报错;对 String Key 执行报 WRONGTYPE。
排错要点:返回 0 时先区分 Key 不存在与值未出现,用 EXISTS/TYPE 进一步判断;删多了通常是 count 误写成 0 或弄反了正负方向;没删干净检查值是否大小写或前后空格不一致;报 WRONGTYPE 时用 TYPE 检查类型;慢日志出现 LREM 时评估列表长度并考虑换数据结构。清理使用DEL tutorial:{lrem}:mylist tutorial:{lrem}:ex tutorial:{lrem}:str tutorial:{lrem}:py tutorial:{lrem}:java。速记:1.0 起支持、count 正头负尾零全部、返回被移除个数、返回 0 表示没删到(Key 不存在或未匹配,二者不可区分)、按字节精确匹配区分大小写、O(N+M) 大列表是慢命令、单命令原子、按值删除直接用 LREM 而不是先 LRANGE 再逐个删。