当前位置:首页 > 问答 > 正文

Redis里查数据又想顺手删了,这一步搞定查询和删除操作

这个想法其实挺常见的,就像你去仓库里拿一个快递包裹,你找到了,顺手就把取件单撕掉扔了,因为你已经拿到了东西,那张单子就没用了,在Redis里,有时候我们也会遇到类似的情况:我们需要根据一个键(key)去查一个值(value),但这个数据可能是一次性的,查过一次之后就没用了,放着反而占地方,或者可能引起重复处理,这时候,如果能查完顺手就把它删了,岂不是一步到位,又干净又利落?

(来源:基于常见的Redis使用场景和开发者实践)

Redis的设计者很贴心,他们早就想到了这个需求,他们提供了一个非常实用的命令,叫做 GETDEL,这个命令的名字就直白地告诉了你它的功能:Get and Delete,也就是“获取并删除”。

在没有 GETDEL 命令之前,我们如果想实现这个效果,需要怎么做呢?那得分成两步走,而且还得小心翼翼地处理,以防出错,大概的步骤是这样的:

第一步,先用 GET 命令把数据查出来,我们有一个键叫 my_task,里面存着一个任务信息,我们就执行 GET my_task

第二步,等我们确认第一步成功,确实拿到了数据之后,再执行 DEL my_task 命令,把这个键值对从Redis里删除掉。

(来源:Redis官方文档对GET和DEL命令的说明)

你看,这虽然是标准做法,但里面藏着个小问题,如果我们的程序在完成第一步之后,在执行第二步之前,突然因为某种原因(比如网络波动、服务器卡了一下)崩溃了,或者被中断了,那会发生什么?结果是,数据我们已经读走了,但删除操作没执行成功,这个本该被清理掉的“一次性数据”就永远留在了Redis里面,成了没人管的“僵尸数据”,下次我们再想找新任务的时候,可能又会读到这个旧任务,导致重复处理,这就出问题了。

Redis里查数据又想顺手删了,这一步搞定查询和删除操作

为了解决这个两步走带来的风险,我们就需要引入更复杂的逻辑,比如使用Redis的事务(MULTI/EXEC)或者Lua脚本来保证这两个操作是连续、不可中断的,但这样一来,代码就变得复杂了,对刚开始用Redis的人来说,理解起来也有点门槛。

(来源:基于分布式系统开发中对于数据一致性的常见处理方式)

正因为有这些不便和潜在风险,Redis从6.2.0版本开始,就正式引入了 GETDEL 这个命令,它就像一个超级帮手,把查询和删除这两个动作原子性地捆绑在了一起,原子性的意思就是说,这两个操作要么一起成功,要么一起失败,不会出现只完成一半的尴尬情况。

我们只需要一个简单的命令:GETDEL my_task,Redis收到这个命令后,会先在内部完成下面一系列动作:

Redis里查数据又想顺手删了,这一步搞定查询和删除操作

  1. 查找键 my_task 是否存在。
  2. 如果存在,就立刻把对应的值返回给我们。
  3. 紧接着,在同一个命令执行流程里,毫不犹豫地把 my_task 这个键值对从数据库中删除。

整个过程是一气呵成的,没有给任何外部干扰留下可乘之机,这样,我们既拿到了想要的数据,又确保了数据不会被二次读取,代码也变得异常简洁,一行命令就解决了之前需要多行代码并且还要考虑异常处理才能搞定的事情。

(来源:Redis 6.2.0版本发布说明及GETDEL命令官方文档)

举个例子,假设我们在做一个简单的消息队列,生产者把任务放进Redis,消费者从里面取任务执行,对于每个任务,我们只希望被一个消费者处理一次,消费者进程就可以使用 GETDEL task_queue 来获取任务,一旦某个消费者成功执行了这个命令,任务数据就被它独占了,并且立即从队列中消失,其他消费者再来取的时候,拿到的就是下一个任务了,完美避免了重复消费。

使用 GETDEL 的时候也有个地方需要注意一下,如果你试图获取并删除一个不存在的键,那么Redis不会报错,它会返回一个空值(nil),同时因为键本来就不存在,删除操作实际上也没什么可做的,所以你的程序需要能处理这种返回空值的情况。

如果你在使用Redis时,经常遇到“读完即焚”的场景,就是那些数据查出来用过之后就可以扔掉的情况,GETDEL 命令绝对是一个你应该放进工具箱的好帮手,它简化了代码逻辑,提升了操作的可靠性,让数据处理变得更加清爽和高效,下次再遇到这种需求,就不用再分两步走啦,直接“查询删除一步到位”就好。

(来源:对GETDEL命令优势的总结性描述)