多个字符串匹配性能

时间:2011-09-22 23:11:46

标签: sql ruby-on-rails database performance redis

我有一个有超过100,000条记录的艺术家表,我用它来匹配用户提交的数组(1到数千)艺术家。我当前的查询如下所示:

SELECT id from artists WHERE lower(name) IN(downcase_artists)

这样做的工作很好,但我想知道它是否可以更快。当匹配成千上万的艺术家时,查询时间在几百毫秒到有时十几秒之间变化。名称列已编制索引。 (这甚至会对字符串列产生影响吗?)

我在想,像Redis这样的东西会加快速度吗?通过保留艺术家名称及其相应ID的键值存储?

我是否还有其他任何可以加快速度的选项?

编辑:正如James建议的那样,我尝试实现某种all_artists缓存方法(使用heroku上的memcache附加组件)并使用它来匹配我的字符串:

artist_ids = self.all_cached.select{|a| downcase_array.include?(a.name)}.collect(&:id)

我获得了非常小的数据库查询时间,但总请求时间大幅增加:

Before: Completed 200 OK in 1853ms (Views: 164.2ms | ActiveRecord: 1476.3ms)  
After: Completed 200 OK in 12262ms (Views: 169.2ms | ActiveRecord: 1200.6ms)

当我在本地运行时,我得到了类似的结果:

Before: Completed 200 OK in 405ms (Views: 75.6ms | ActiveRecord: 135.4ms)
After: Completed 200 OK in 3205ms (Views: 245.1ms | ActiveRecord: 126.5ms)

将ActiveRecord时间放在一边,看起来从查询中取出字符串匹配会使我的问题变得更糟(而且只有100个字符串)。或者我错过了什么?

我也看过像Sphinx这样的全文搜索引擎,但它们听起来有点矫枉过正,因为我只搜索了一个单列......

编辑2 :我终于设法将请求时间减少到

Before: Completed 200 OK in 1853ms (Views: 164.2ms | ActiveRecord: 1476.3ms)  
Now: Completed 200 OK in 226ms (Views: 127.2ms | ActiveRecord: 48.7ms)

使用json字符串的redis存储(see full answer

5 个答案:

答案 0 :(得分:2)

如果我没记错的话,使用IN可能会非常昂贵。怎么样:

caches_action :find_all_artists

def gather_artist_ids
  @all_artists = Artist.all(:select => "id,name)
end

然后,无论您想要查询的地方:

@downcase_artists = "Joe Schmo, Sally Sue, ..."
@requested_artists = @all_artists.select{|a| @downcase_artists.include?(a)}.collect(&:id)

您可以在:gather_artist_ids上执行cache_action,并使您的清扫程序仅触发after_create,after_update和after_destroy。

的MongoDB: 我通过Mongoid使用MongoDB并且其中有151万条记录,正则表达式搜索 / usersinput / i 需要不到100毫秒的索引。它非常快。

答案 1 :(得分:1)

由于您将艺术家的名字以小写字母存储,并且您正在搜索完整的艺术家姓名,因此这应该适合您。我将说明Redis命令,您应该可以在客户端轻松找到相应的API调用(首先使用redis-cli,它会为您清理)。

我认为你的表Artists有3条记录:'The Kind of Kindo','Dream Theatre'和'A.C.T',相应的ID 1,2,3。

基本思想是在sorted set中加载该表。每个成员的score将是艺术家的ID,成员字符串将是艺术家的名字:


加载阶段,填写所有艺术家的排序集(注意小写):

ZADD artists 1 "the reign of kindo"
ZADD artists 2 "dream theater"
ZADD artists 3 "a.c.t"

现在我将向Redis查询前两个乐队。我们的想法是在这个时间构建一个临时的有序集(query:10),它将与artists有序集相交。

为什么不直接使用query?我正在为每个查询分配一个(任意)id,因此并发用户搜索之间没有冲突!此外,我们可以在稍后缓存结果集一段时间时参考id(更多内容见下文)。

建议使用:作为分隔符(外观here)。


查询阶段,填写查询排序集。

ZADD query:10 0 "the reign of kindo"
ZADD query:10 0 "dream theater"
ZINTERSTORE result_query:10 2 artists query:10 WEIGHTS 1 0
EXPIRE result_query:10 600

查询排序集的得分无关紧要,因此它可以是0

使用ZINTERSTORE,我们会在result_query:10 2个密钥,artistsquery:10的交集处存储。但是有一个问题!两个键的得分如何组合成最终的排序集?

答案:Redis默认总和

现在,我们可以使用WEIGHTS属性我们不想要的分数。因此,WEIGHTS 1 0表示只会将artists的得分相加。

现在我们在result_query:10中有匹配的艺术家,EXPIRE使其持续10分钟。您可以找出一种使用此缓存的智能方法=)


获取结果集

完成上述所有操作后,您可以使用ZRANGE获得所需的结果:

redis> zrange result_query:10 0 -1 withscores
1) "the reign of kindo"
2) "1"
3) "dream theater"
4) "2"

间隔0 -1表示获取所有成员withscores属性使ZRANGE返回每个成员的ID(分数)及其字符串。

希望一切都有道理。这只是Redis的冰山一角。很好的基准测试,看看你!

答案 2 :(得分:0)

我会考虑一个全文搜索引擎(Sphinx,Ferret,Lucene等),其中一些最终会为您提供更有趣的搜索功能。除非你总是只想搜索艺术家姓名等。

然后我会考虑只保留一大堆可用于永久缓存名称的内存并点击它而不是数据库。

答案 3 :(得分:0)

从查询中删除“lower(..)”函数。

答案 4 :(得分:0)

我最终使用Redis不仅存储艺术家ID和名称,而且还存储了返回给用户的整个json响应。我的Redis哈希看起来像这样:

{"all_artists" => ["artistname1" => "json_response1", "artistname2" => "json_response2"...]}

我使用以下(redis-rb gem)进行匹配:

REDIS.hmget("all_artists", *downcase_array)

返回相应艺术家的所有json字符串(包括艺术家ID,名称和即将举行的音乐会),而不会访问数据库。每当艺术家或音乐会更新时,我显然都会更新Redis哈希。

由此产生的时差(对于100位艺术家):

Before: Completed 200 OK in 1853ms (Views: 164.2ms | ActiveRecord: 1476.3ms)  
Now: Completed 200 OK in 226ms (Views: 127.2ms | ActiveRecord: 48.7ms)

还有一些优化还有待完成,但字符串匹配现在绝对不合适了。