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

Redis里没直接方法通过值找键,咋整比较靠谱呢?

Redis被设计成一个键值数据库,它的核心优势在于通过键来闪电般地查找值,键就像是值的唯一身份证号,查询起来非常高效,反过来,想通过一个值(张三”)去找到所有存着这个值的键,Redis确实没有提供像GETKEY这样的直接命令,这是因为值本身没有索引,如果硬要扫描,就得把整个数据库翻个底朝天,这在数据量大的时候简直是性能灾难。

没有现成的方法,我们该怎么办呢?其实思路就是“自己动手,丰衣足食”,核心思想是在应用层面,通过一些设计技巧,来构建一个类似于“反向索引”的机制,以下是几种比较靠谱的常见做法,从简单到复杂,你可以根据实际情况选择。

第一种方法:自己维护一个反向索引(最常用、最靠谱)

这是最推荐的方法,既然Redis不帮我们做,我们就自己用Redis的数据结构再建一个“目录”,思路非常简单:在你用SET user:1001 "张三"这样的命令存储正向关系的同时,再维护一个集合(Set)或列表(List),专门用来记录所有值为“张三”的键。

  • 具体操作
    1. 当你设置一个键值对时,比如SET user:1001 "张三"
    2. 紧接着,你执行一个SADD index:张三 user:1001,这里,index:张三就是一个反向索引的键,它的类型是集合(Set),里面存放了所有值为“张三”的用户的键。
  • 如何查询: 当你想找值等于“张三”的所有键时,只需要一个简单的SMEMBERS index:张三命令,Redis就会立刻返回user:1001,甚至user:2005等所有相关的键。
  • 优点
    • 查询速度极快:和正常的键查询一样,是O(1)的时间复杂度。
    • 非常灵活:你可以用集合的交集(SINTER)、并集(SUNION)等操作实现复杂的查询,找出既是‘张三’又是‘北京’的用户”。
  • 缺点与注意事项
    • 需要额外存储空间:相当于用空间换时间。
    • 需要保证数据一致性:这是最关键的一点,当你更新user:1001的值从“张三”改为“李四”时,你必须同时完成两个操作:1. 从index:张三集合中移除user:1001;2. 向index:李四集合中添加user:1001,这个操作最好放在一个事务(例如MULTI/EXEC)或Lua脚本中执行,确保原子性,避免数据不一致。

第二种方法:使用有序集合(Sorted Set)进行范围查询

如果你的需求不仅仅是精确匹配,还包含“查找分数大于90的学生”这类基于值的大小范围的查询,那么有序集合就派上用场了。

  • 具体操作: 你可以不直接用值作为键,而是把值作为有序集合的分数(Score),把原本的键作为成员(Member),学生分数可以这样存:ZADD student:scores 95 user:1001 88 user:1002
  • 如何查询: 要查找分数等于95的所有学生键,可以用ZRANGEBYSCORE student:scores 95 95,要查找大于90分的,用ZRANGEBYSCORE student:scores 90 +inf
  • 优点

    非常适合数值型值的范围查询。

  • 缺点

    和第一种方法一样,需要维护额外的数据结构,并保证数据一致性。

第三种方法:万不得已时使用的“大杀器”——SCAN命令(非常不推荐常规使用)

如果你的应用偶尔才需要做一次这种反向查找,而且数据量不大,或者你只是想在运维时临时排查问题,又没有任何预先建立的反向索引,那么SCAN命令是唯一的选择,但必须极度谨慎使用

  • 具体操作SCAN命令可以增量式地遍历数据库中的所有键,你需要遍历所有键,然后用GETTYPE判断其值是否匹配你的目标。
  • 为什么极其不推荐
    • 性能极差:它会扫描整个数据库,如果数据库有几百上千万个键,这个操作会长时间占用Redis服务器资源,可能导致其他正常请求被阻塞或变慢,就像在图书馆里为了找一本特定内容的书而把所有的书从头到尾翻一遍。
    • 不实时:在遍历过程中,如果有键被修改或删除,可能会得到不一致的结果。
  • 如果非用不可的注意事项
    • 一定要在Redis服务器负载低的时候(比如深夜)进行。
    • 使用SCAN而不是已经废弃的KEYS命令,因为KEYS是直接一次性返回所有匹配键,在数据量大时会直接导致Redis服务卡死,而SCAN是分批次、游标式的,对服务影响相对小一些。
    • 在从库(slave)上执行,避免影响主库的读写性能。

总结一下

回到问题“Redis里没直接方法通过值找键,咋整比较靠谱呢?”,最靠谱的答案就是:不要等到需要的时候才去硬找,而应该在设计数据存储方案时,就根据业务查询需求,主动地、有预见性地创建反向索引。

  • 对于精确匹配查询:优先选择自己维护一个Set或List作为反向索引。
  • 对于范围查询:考虑使用有序集合(Sorted Set)
  • 对于临时、紧急的运维排查:在明确知晓风险的前提下,极其谨慎地使用SCAN命令

这种“空间换时间”和“将查询模式设计在数据结构中”的思想,不仅是解决Redis这个特定问题的关键,也是应对很多NoSQL数据库设计时的通用法宝,在Redis的世界里,预先的巧妙设计远比事后的暴力查找要靠谱得多。

Redis里没直接方法通过值找键,咋整比较靠谱呢?