尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Redis批量删除键值对实战:从SCAN游标到Lua脚本的安全方案
折腾过 Redis 键值对清理的人应该都有同感平时读写数据都挺顺利一旦要按特定模式批量删除键就开始纠结了。比如项目里出现了一大堆以user:temp:开头的脏数据或者某次上线写错了缓存前缀积压了几十万个cache:v1:*之类的键。手工一个个删不现实直接匹配删除又怕把线上搞崩。这篇文章我会把 Redis 批量删除键值对这件事讲透从KEYS的坑讲起逐步拆解SCAN、xargs、Lua 脚本这几种主流做法附带生产环境踩坑记录和防误删清单。适合后端开发、运维以及所有需要做 Redis 缓存治理的同学参考。1. 批量删除的整体思路为什么方案选型决定成败1.1 从一次生产事故说起KEYS的代价先说一个很多新手容易犯的错。Redis 是单线程模型所有命令都是排队执行的一个命令只要耗时足够长它后面排队的请求就会全部卡住。KEYS user:*这条命令的复杂度是 O(N)N 是整个键空间的数量。如果你的实例里有两三百万个键即使只是匹配其中一小部分Redis 也得把全部键遍历一遍才能告诉你结果。这个过程短则几秒长则几十秒期间线上所有读写都会阻塞。我见过最典型的一次事故是同事为了清理一批测试数据直接在备用节点上敲了KEYS *后来因为主从切换这条命令转移到了主库上。结果从那一刻开始整个服务的接口延迟从 5 毫秒一路飙到 3 秒以上持续了将近半分钟才恢复。排查到最后原因就是这条KEYS。所以请记住结论生产环境永远不要用KEYS命令去匹配键除非你的实例只有几十个键并且你非常清楚自己在做什么。这类事故的本质在于我们需要的不是“一次性找出所有匹配键”而是“在不阻塞主线程的前提下分批拿到匹配键并删除”。理解了这一点后面的所有方案就顺理成章了。1.2 替代思路SCAN游标与分批处理Redis 为了解决KEYS的阻塞问题从 2.8 版本开始提供了SCAN系列命令。它用游标的方式迭代键空间每次调用只返回一小部分键并给你一个新的游标直到游标归零表示扫描完成。SCAN虽然底层也是遍历但每次执行只处理一小部分槽位耗时可控不会长时间霸占主线程。你可以把SCAN理解为图书馆管理员拿着一沓索引卡每次只翻几十张翻完告诉你“我下次从第几张继续”。整个过程中其他人查书借书都不受影响。这就是它和KEYS的本质区别KEYS是管理员把所有书翻完才放你走SCAN是每隔一会儿就把通道让出来。有了游标机制批量删除的思路就清晰了循环调用SCAN每次拿到一批键立即用DEL删掉然后拿着新游标继续扫直到游标为 0。这样就把一次巨大的阻塞操作拆成了无数个微小的、可接受的命令Redis 单线程模型下的风险被摊薄了。需要注意SCAN在遍历过程中如果键集合发生变化它不保证一定返回所有键官方文档明确说明了这一点。不过对于删除清理场景来说只要接受极少量新增键的漏网问题不大。1.3 主流方案横向对比与适用场景基于上面的思路目前业界常用的批量删除方案大致有三类。第一类是纯命令行组合用redis-cli --scan配合xargs管道删除适合临时清理和脚本化操作。第二类是 Lua 脚本把SCAN和DEL封装在 Redis 服务端执行减少网络往返适合需要原子性和可控节奏的场景。第三类是客户端脚本比如用 Python 的scan_iter或 pipeline 包一层适合需要复杂逻辑、需要日志和统计的日常治理任务。方案核心实现优点缺点命令行管道redis-cli --scan配合xargs调用 DEL一条命令搞定无开发成本每批命令重启 redis-cli 进程中小规模够用Lua 脚本SCAN加DEL服务端循环原子性强网络往返少脚本内循环要控制批次避免超时客户端脚本scan_iter配合pipeline可统计、可限速、可扩展需要额外语言环境和依赖选型的时候没有绝对最优关键是看键量级和团队技术栈。如果只是临时清理几千个键命令行管道足够。如果有一套周期性的缓存治理任务我更倾向 Lua 或客户端脚本因为可以记录每次删了多少也能加保护逻辑。2. 核心细节解析与实操要点2.1 SCAN命令的正确姿势游标、MATCH与COUNTSCAN的命令格式是SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]。第一个参数是游标初始为 0。MATCH用于指定模式匹配COUNT是每一轮迭代返回键数量的期望值注意这里只是“期望”实际返回条数可能多一些或少一些这不影响正确性只影响每轮的耗时和吞吐。SCAN的MATCH支持的是 glob 风格通配符不是正则表达式。常用的有*匹配任意多个字符、?匹配单个字符、[]匹配字符集合。比如session:*匹配所有以session:开头的键user:?匹配user:后面只有一个字符的键。特别提醒如果模式本身包含*这类特殊字符要想清楚匹配意图否则很容易误删不该删的数据。COUNT的取值没有标准答案。实例中键总数少、单个键值体积小可以设大一点反之设小一点。实测下来常规清理场景用 100 到 1000 都行。如果你在SCAN上加了COUNT 10000单次返回的键多了后续DEL的批次也要跟着调整否则反而容易累积内存。还有一个隐蔽的细节SCAN并不保证键不重复返回同一个键在迭代过程中可能被返回多次但DEL删除重复键是幂等的不影响最终结果所以批量删除场景不需要为此担忧。2.2 最高效率的单行命令redis-cli --scan 与 xargs 的组合最简单的方案其实不需要写任何代码一条管道命令就能完成。先看最基础的版本redis-cli --scan --pattern session:* | xargs -r redis-cli DEL这条命令的思路很直接redis-cli --scan内部会不断执行 SCAN 游标迭代把所有匹配的键名输出到标准输出一行一个xargs读入这些键名作为参数传给redis-cli DEL。注意这里用的是--scan而不是--keys因为前者底层是安全的SCAN后者底层是危险的KEYS。有三个细节值得展开。第一xargs的-r参数很关键它表示“输入为空则不执行命令”避免出现ERR wrong number of arguments for del command这种报错。第二如果键名可能包含空格默认的xargs会按空格切分把明明是一个键的名称拆成两半。Linux 下可以用xargs -d \n跨平台更稳的写法是先转成\0分隔redis-cli --scan --pattern session:* | tr \n \0 | xargs -0 -r redis-cli DEL第三不加-n时xargs虽然会自动分批但每批可能只有 1 个键删除效率偏低。实测中我通常这样写redis-cli -n 0 --scan --pattern session:* | xargs -r -n 100 redis-cli -n 0 DEL-n 100表示每 100 个键作为一组参数传给redis-cli DEL一条命令删 100 个键。键值都很小时-n可以提到 500 甚至 1000但如果键很大建议调小批次避免单条 DEL 命令执行时间过长。另外如果目标键可能是大 key把DEL换成UNLINK更稳妥这是 Redis 4.0 之后引入的异步删除命令内存回收在后台完成不会阻塞主线程。2.3 Lua脚本原子化清理SCAN DEL 服务端封装命令行管道方案虽然简单但有一个天生短板每批 SCAN 在客户端完成每批 DEL 又要重新建立连接大规模清理时网络开销和进程创建开销很可观。另一个短板是它缺少统计信息删了多少你不知道中途出错也不容易定位。这时候 Lua 脚本就派上用场用redis-cli --eval执行本地脚本把 SCAN 迭代和 DEL 删除都放到服务端。一个可用的脚本如下假设保存为batch_delete.lualocal cursor 0 local pattern ARGV[1] local batch tonumber(ARGV[2]) local deleted 0 repeat local result redis.call(SCAN, cursor, MATCH, pattern, COUNT, batch) cursor result[1] local keys result[2] if #keys 0 then deleted deleted redis.call(DEL, unpack(keys)) end until cursor 0 return deleted执行方式redis-cli -n 0 --eval batch_delete.lua , session:* 100注意--eval的语法里逗号前面是 KEYS 数组逗号后面是 ARGV 数组这里我把匹配模式放在 ARGV 里。脚本每轮 SCAN 拿到一批键后立刻 DEL整个过程在服务端原子执行外部请求不会穿插进来。这里的batch建议控制在 100 左右因为脚本内部是循环执行批次太大容易让单个命令执行时长上升引起主线程短暂卡顿。Redis 默认内嵌 Lua 5.1可以直接用unpack不需要改成table.unpack。3. 实操过程与核心环节实现3.1 场景设定与前置检查先摸清要删多少我接手过的一个项目因为缓存 key 设计变更线上积累了大约 26 万个cache:product:*开头的旧缓存键。这些键已经没有业务读取但一直占着内存RDB 持久化文件也越来越大。这种清理场景很典型就是标准的缓存治理。动手前第一步永远先数数用 SCAN 统计匹配键的数量redis-cli -n 0 --scan --pattern cache:product:* | wc -l如果返回几十万行说明规模不小。然后抽样几个键看看内容确认这个模式确实只包含目标键redis-cli -n 0 --scan --pattern cache:product:* | head -20我还会顺手看键的 TTL 分布。如果这些键本身设置了过期时间其实不用急着手动删等它们自然过期就行手动删只是提前释放内存。如果很多键是永久键没有任何 TTL那就属于必须清理的目标。最后看一眼内存基线和数据库键总数方便清理后对比redis-cli -n 0 INFO memory redis-cli -n 0 DBSIZE记下used_memory和dbsize的数值清理完回来对比就能快速判断效果。3.2 正式执行清理三种方案的落地步骤方案一是单行命令清理。对 20 多万个键我直接用 xargs 版本删改用UNLINKredis-cli -n 0 --scan --pattern cache:product:* | xargs -r -n 200 redis-cli -n 0 UNLINK命令跑的时候终端会有大量输出每删一批返回一个整数表示这批删除了多少个键。看到输出逐渐变少并最终停止就说明 SCAN 已经走完迭代所有匹配的键都被处理了。方案二是用 Lua 脚本拿总数。如果我想明确知道总共删了多少可以接收脚本返回值redis-cli -n 0 --eval batch_delete.lua , cache:product:* 100脚本返回一个数字就是实际删除的键数量。这里要留意脚本执行期间如果还有业务写入相同模式的键返回值和清理前统计的数量可能对不上这是正常的。方案三是客户端脚本控制节奏。如果清理的键模式涉及核心业务我不会让命令一口气跑完而是把删除速率控制住避免瞬时流量冲击。Python 里可以这样写import time import redis r redis.Redis(hostlocalhost, port6379, db0) pipe r.pipeline(transactionFalse) count 0 for key in r.scan_iter(matchcache:product:*, count100): pipe.unlink(key) count 1 if count % 500 0: pipe.execute() print(fcleaned {count} keys) time.sleep(1) pipe.execute() print(ftotal cleaned {count} keys)每删 500 个键就提交一次 pipeline并 sleep 1 秒。这个节奏可以根据线上压力调整压力大的时候每 200 个键提交一次sleep 拉长到 3 秒。这种“带着刹车往下删”的方式在白天业务高峰期清理时特别好用。3.3 删除后的内存与效果验证清理完不是结束必须验证是否真的干净了。第一件事重新扫描确认匹配模式已经没有遗留键redis-cli -n 0 --scan --pattern cache:product:* | wc -l这里有一个隐蔽点如果在清理期间仍有新写入这个数字可能不是 0要结合业务写入量判断。我一般隔几秒再跑一次如果数字稳定且接近 0才算清理完成。第二件事看内存变化redis-cli -n 0 INFO memory有时你会发现used_memory并没有像预期一样大幅下降甚至几乎没变。这不一定代表删除失败了。Redis 常用的内存分配器比如 jemalloc在释放内存后不一定立刻把内存归还给操作系统而是留在进程内存池里继续复用。所以更真实的指标是used_memory_rss也就是进程实际占用的物理内存以及mem_fragmentation_ratio碎片率。如果删除后mem_fragmentation_ratio偏高说明内存碎片较多。维护窗口中可以试用MEMORY PURGE强制整理但注意这个命令只在 jemalloc 分配器下有效执行期间也会有一些性能开销不要频繁调用。另外如果持久化策略是 RDB可以观察清理后下一次 RDB 文件生成的大小那才是真正反映数据体积下降的指标。4. 常见问题与排查技巧实录4.1 常见问题速查表我把实际运维中高频出现的问题整理成速查表可以对着查问题现象可能原因解决办法执行 KEYS 后 Redis 卡顿KEYS 遍历阻塞单线程改用 SCAN 或--scanxargs 删除效率很低每批 DEL 只传了 1 个键加-n 100批量传参xargs 报 DEL 参数错误输入流为空DEL 无参数加-r参数匹配不到任何键模式被 Shell 通配符展开用单引号包裹模式删除后 used_memory 没降内存分配器未归还 OS看 rss 和碎片率窗口期 MEMORY PURGE大 key 删除瞬间延迟飙升DEL 同步回收大键内存换成 UNLINK集群模式删不干净SCAN 只在单节点执行逐节点执行或借助集群代理Lua 脚本执行超时批次过大或键过多调小 COUNT分批执行这些坑我基本都踩过或者见同事踩过最典型的就是 Shell 通配符展开。如果你写redis-cli --scan --pattern cache:*时忘了引号Shell 会先把cache:*当作文件名来匹配模式被拆得乱七八糟。这种问题排查起来特别费时间因为命令看起来没错行为却完全不对。4.2 大Key删除的阻塞问题与UNLINK有一个问题在批量删除场景里特别容易被低估大 key。如果你的模式匹配到了某些超大的哈希表、列表或者字符串用DEL删除时主线程需要同步遍历回收这些内存耗时可能达到几百毫秒甚至几秒。在生产环境里这就是一次明显的服务抖动。Redis 4.0 引入的UNLINK命令会先把 key 从键空间解除链接真正的内容再交给后台线程异步回收。调用方几乎瞬间拿到返回主线程不会卡住。批量清理时如果不确定匹配的键里有没有大 key可以先用内置统计工具扫一遍redis-cli -n 0 --bigkeys--bigkeys会扫描并统计最大的若干键类型和大小提前识别风险。确认存在大 key 时删除一定要用UNLINK而不是DEL。另外批量删除时即使单个 key 不大如果一批传了几千个键DEL 的累计耗时也会很明显。所以我一直强调批次控制-n 100或 Lua 里COUNT 100都是相对稳妥的起始值。批次规模和单键大小要一起考虑不能只盯着数量。4.3 集群环境与多库场景的注意事项如果你的 Redis 是集群模式情况会更复杂。Redis Cluster 把键分散到多个节点每个节点只负责一部分哈希槽。SCAN和KEYS在单个节点上只能看到该节点的键不能跨节点遍历。所以你在任意一个节点执行--scan --pattern cache:*拿到的永远只是部分节点上的键。集群环境做模式清理常见做法是逐节点执行同样的扫描和删除。如果节点不多可以手动在各个节点上跑同一套命令如果节点很多可以借助运维平台把命令分发到所有主节点执行。需要特别注意分发时每个节点必须独立记账和验证不能共享游标状态。另外一个细节是选库。单机 Redis 默认有 16 个 databaseredis-cli默认连的是 db 0。如果你的键在 db 3不加-n 3参数扫描结果会是空的。这类“命令没报错但啥也没删”的问题大多出在选库上。4.4 误删防护与缓存雪崩规避经验最后说说怎么防止误删和删出事故。虽然主题是批量删除但安全永远是第一位的。第一生产环境动手前先备份。RDB 持久化如果已经开启在维护窗口手动执行一次BGSAVE确保有可回滚的备份文件。别嫌麻烦误删一个模式下的键恢复成本和影响范围远大于备份成本。第二模式要尽量严格。删除模式写成wms:order:cache:*这样的精确前缀不要图省事写*order*。宽泛模式很容易匹配到不该删的线上数据。第三先打样再动手。批量删除前先跑redis-cli -n 0 --scan --pattern xxx:* | head -50人眼确认返回的都是目标键。有条件的话在测试环境用同样的模式先演练一遍。第四不要忽略缓存雪崩。这些键删除后业务如果重新读取会全部打到数据库上。如果目标键数量很大相当于瞬间制造一场小型缓存雪崩。我的经验是分批限速删除或者更温和地用EXPIRE把键的 TTL 设置为一个较短时长让它们分散过期再在下一个周期清理遗留键。这样既能释放内存又能平滑流量冲击。最后说一点个人体会。Redis 批量删除这件事技术上没有任何炫技成分真正的难点是选对工具和方法并且把安全措施做足。我在好几个项目里见过因为一条KEYS或者一次莽撞的 DEL 把线上搞挂的例子也见过用对 SCAN 和 UNLINK 之后几百万键的清理就像散步一样轻松。如果你看完这些内容只记住一句话那就是生产环境删除键值对永远用 SCAN 游标迭代永远分批处理永远先备份。这句话救了我不止一次。
RELATED

相关推荐

网络新词“直域”解析:从直男聚集地到直球沟通场域

网络新词“直域”解析:从直男聚集地到直球沟通场域

“什么叫直域?”这个问题我最近在好几个评论区里都撞见了,有聊游戏的在问,聊职场的在问,连追星的超话里都有。查遍百科没有标准词条,问身边的年轻人,每人给的答案还不一样。越是这样越有意思——网络新词从…

📅 2026/10/2 3:10:09
YOLOv5鸡蛋检测全链路实战:从标注、训练到PyQt界面与树莓派部署

YOLOv5鸡蛋检测全链路实战:从标注、训练到PyQt界面与树莓派部署

简介:本资源是一套面向计算机视觉初学者与农业智能化实践者的YOLOv5鸡蛋目标检测完整开发包,解决农产品自动化识别与计数场景中的模型训练与部署需求。压缩包共643个文件,涵盖182张标注图像(JPG)、163份PASCAL VOC格式…

📅 2026/10/2 3:10:09
工业级鸡蛋破损检测数据集:YOLOv12小目标实战指南

工业级鸡蛋破损检测数据集:YOLOv12小目标实战指南

简介:本资源是面向计算机视觉初学者与工业检测算法工程师的鸡蛋破损检测专用数据集,聚焦农业与食品加工场景下的YOLO目标检测任务,解决禽蛋分拣中破损、裂纹等异常状态的自动化识别难题。数据集共904张高质量真实场景图像,覆盖nor…

📅 2026/10/2 3:10:09
MORE NEWS

更多资讯

📰

MFC/VS截屏实战:GDI BitBlt原理与避坑指南

简介:面向MFC/C开发者的屏幕截图功能实现资源,基于Visual Studio环境,完整演示如何捕获整个屏幕或指定窗口,并将其保存为BMP/JPEG文件。压缩包共22个文件,其中6个.h头文件与3个.cpp源文件承载核心逻辑,1个.…

📰

OpenRig:基于Node.js+tmux+YAML的轻量级本地AI开发工作流

1. OpenRig 是什么:一个被误读的开源项目名与真实技术图谱OpenRig 这个词在当前中文技术社区里,正经历一场典型的“语义漂移”——它既不是某个广为人知的成熟开源项目(比如 OpenCV、OpenSSH 那样有明确官网、GitHub star 数和文档体系&#…

📰

从WiFi 4到WiFi 7:协议命名、技术演进与路由器选购指南

我最近在整理WiFi系列基础内容,写到第三篇,正好是大家问得最集中的地方:802.11ac、802.11ax、802.11be这些协议名,和路由器包装上印的WiFi 5、WiFi 6、WiFi 7到底怎么对应?市面上那么多数字,到底哪个才是现…

📰

光纤环形器从原理到选型:单向传输控制与工程实战要点

光纤环形器这个东西,做光通信的应该都不陌生,但说实话,很多人对它也就是停留在“认识”的阶段——知道它能单向传光,知道它常用于OTDR和WDM系统,但真要问到它内部是怎么工作的、怎么选型、为什么某些场景必须用它而不是…

📰

React Native鸿蒙动画实战:Animated上下滑动入场踩坑与优化

把React Native应用跑到鸿蒙设备上,这个流程现在其实很成熟了:改一下入口配置,用适配层的原生容器去加载JS bundle,大部分业务页面能直接跑起来。但真正让团队头疼的往往是动画。尤其是上下滑动入场这类最常用的交互动效——列表卡…

📰

挖掘机检测模型训练:VOC数据体检与YOLO格式转换实战

简介:面向计算机视觉与目标检测学习者的挖掘机图像数据集,包含约700张已完成人工标注的图片,符合VOC标准标注格式,可直接用于训练YOLO等目标检测模型,也可转换为COCO或其他框架格式;聚焦工程车辆典型场景&a…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬