假设我们有一个简单的mysql表(用户),其字段为:
id
rating
salary
我希望得到10个具有最高评级和工资且指定范围(50-100)的用户,即在mysql中它将是
SELECT id from user WHERE salary>50 and salary<100 ORDER by rating limit 0, 10
在100K用户表上运行20ms。
假设我在redis中有相同的内容: Zlist评级(评级=&gt; user_id) Zlist工资(salary =&gt; user_id)
我用redis看到的所有解决方案都包括复制100k工资Zlist,删除不需要的条目,以及合并100k评级列表,如
zinterstore 1 search salary
zremrange search -inf 50
zremrange search 100 +inf
zinterstore 2 search rating weights 0 1
zrange search 0 10
这绝对很慢(为什么要复制100k元素才能删除大部分元素?)。
有没有办法用redis实现这个至少相对有效的效率?
答案 0 :(得分:3)
您描述的用例无法在NoSQL解决方案中优雅地建模。这不是Redis的限制。
让我解释一下。您正在一个字段上运行范围查询,并在另一个字段上进行排序。这不是NoSQL解决方案擅长的。例如,Google App Engine禁止此类查询。请查看GAE Query Restrictions并阅读“不等式过滤器中的属性必须在其他排序顺序之前排序”部分
要获得与不等式过滤器匹配的所有结果,查询会扫描 索引表为第一个匹配的行,然后返回所有连续的 结果,直到找到不匹配的行。连续 要表示完整结果集的行,必须按行排序 其他排序顺序之前的不等式过滤器。
话虽如此,您仍然可以高效地运行查询,但解决方案并不优雅。
users_with_salary:10000-15000
的集合。此集将包含在给定范围内具有薪水的用户ID。
String userids[];
for(rating = 10; rating > 0; rating--) {
for(salary = min_salary; salary < max_salary; salary += 5000) {
String salary_key = "users_with_salary:" + salary + "-" + (salary+5000);
String rating_key = "users_with_rating:" + rating + "-" + (rating+1);
userids.append(redis.sinter(salary_key, rating_key));
if(userids.length > 10) {
break;
}
}
}
使用redis 2.6和lua脚本,你甚至可以在lua服务器上运行它。
总之,如果要对数据运行复杂查询,最好在关系数据库中对其进行建模。
答案 1 :(得分:2)
使用脚本,您可以使用“ZRANGEBYSCORE薪水50 100”来获得薪水在50到100之间的用户,并将结果存储到tmp集中。假设您将用户的评级存储在密钥“user:[id]”的哈希中,则可以执行“SORT tmp BY user:* - &gt; rating LIMIT 0 10”。
不幸的是,您目前无法排序与zset中的条目相关联的分数,因此您需要将评级值仅存储或另外存储在单独的散列中以使用此方法。
当然,您也可以使用“ZINTERSTORE tmp2 2 rating tmp WEIGHTS 1 0”然后使用“ZRANGE tmp2 0 10”,但这比使用SORT要低得多,因为它需要分配所有tmp2的开销(因为正在创建)而SORT with LIMIT使用部分快速排序算法,该算法实际上只对实际返回的10个结果进行排序。您可能希望保持tmp2,以便您可以快速返回该范围内的其他用户,但在这种情况下,存储按等级排列的工资在50到100之间的临时zset用户可能有意义。
我认为我描述的SORT方法实际上在算法上与SQL数据库可以实现的一样好。一旦使用索引按一个字段上的范围进行过滤,我就知道不能使用另一个字段上的索引来提高对小结果集进行排序的效率。我相信SQL数据库只会使用部分快速排序或等效方法来仅对返回的结果进行排序。