是什么让这个查询在Rails 3中效率低下

时间:2013-09-14 22:15:33

标签: ruby-on-rails ruby-on-rails-3 sqlite activerecord

我一直在努力解决同样问题 - 在rails中执行高效查询。我目前正在尝试对具有500,000条记录的模型执行查询,然后提取有关返回结果的一些描述性统计信息。

概述: 我想提出一些符合一系列标准的产品。我愿意......

  • 计算记录数量(如果没有我想要执行某些操作)
  • 确定匹配记录的最高和最低价格,并计算落在特定范围之间的项目数

目前这套命令比我希望的要长很多(26000ms在我的桌面计算机上本地运行)并涉及8或9个活动记录操作,每个操作大约需要3000毫秒

我是否有错误的做法让这个过程变得如此缓慢?任何建议都很棒

我的控制器中的代码是:

    filteredmatchingproducts = Allproduct.select("id, product_name, price")
    .where('product_name LIKE ? 
    OR (product_name LIKE ? AND product_name NOT LIKE ? AND product_name NOT LIKE ?       AND product_name NOT LIKE ? AND product_name NOT LIKE ? AND product_name NOT LIKE ?) 
    OR product_name LIKE ? OR product_name LIKE ? OR product_name LIKE ? OR product_name LIKE ? OR (product_name LIKE ? AND product_name NOT LIKE ?) OR product_name LIKE ?', 
    '%Bike Box', '%Bike Bag%', '%Pannier%', '%Shopper%', '%Shoulder%', '%Shopping%', '%Backpack%' , '%Wheel Bag%', '%Bike sack%', '%Wheel cover%', '%Wheel case%', '%Bike case%', '%Wahoo%', '%Bicycle Travel Case%')
    .order('price ASC')

    @selected_products = filteredmatchingproducts.paginate(:page => params[:page])  

    @productsfound = filteredmatchingproducts.count
    @min_price = filteredmatchingproducts.first
    @max_price = filteredmatchingproducts.last

    @price_range = @max_price.price - @min_price.price

    @max_pricerange1 = @min_price.price + @price_range/4
    @max_pricerange2 = @min_price.price + @price_range/2
    @max_pricerange3 = @min_price.price + 3*@price_range/4
    @max_pricerange4 = @max_price.price 

    if @min_price == nil
    #don't do anything - just avoid error
    else

    @restricted_products_pricerange1 = filteredmatchingproducts.select("price").where('price BETWEEN ? and ?', 0 , @max_pricerange1).count
    @restricted_products_pricerange2 = filteredmatchingproducts.select("price").where('price BETWEEN ? and ?', @max_pricerange1 + 0.01 , @max_pricerange2).count
    @restricted_products_pricerange3 = filteredmatchingproducts.select("price").where('price BETWEEN ? and ?',  @max_pricerange2 + 0.01 , @max_pricerange3).count
    @restricted_products_pricerange4 = filteredmatchingproducts.select("price").where('price BETWEEN ? and ?',  @max_pricerange3 + 0.01 , @max_pricerange4).count
    end

EDIT 为清楚起见,我的基本问题是 - 为什么每个查询都需要在大型Allproduct数据库上执行,是不是有办法对前者的结果执行后面的查询(即使用过滤匹配产品本身不重新计算它为每个查询)?在其他编程语言中,我习惯于能够记住变量并执行那些记忆值的操作,而不必在执行操作之前再次处理它们 - 这不是Rails中的心态吗?

4 个答案:

答案 0 :(得分:2)

您共享的代码段中有太多错误。最重要的可能是,这不是特定于轨道的优化问题,而是数据库结构和优化问题。

您正在使用'喜欢'查询,两边都有&符号(%),导致SQLLite中的线性搜索时间,因为不能应用索引。理想情况下,您不应该使用' Like'来应用搜索,而应该定义一个product_categories表,该表将在AllProducts表中作为product_category_id引用,并且将在其上定义索引。

要初始化@products_found,@ min_price和@max_price变量,您可以执行以下操作:

filteredmatchingproductlist = filteredmatchingproducts.to_a
@productsfound = filteredmatchingproductlist.count
@min_price = filteredmatchingproductlist.first
@max_price = filteredmatchingproductlist.last

当您在Array而不是ActiveRecord :: Relation上执行这些操作时,这将避免为它们触发单独的查询。

由于结果已排序,您可以在filteredmatchingproductlist数组上应用良好的旧二进制搜索,并计算计数以获得与代码的最后四行相同的结果:

@restricted_products_pricerange1 = filteredmatchingproducts.select("price").where('price BETWEEN ? and ?', 0 , @max_pricerange1).count
@restricted_products_pricerange2 = filteredmatchingproducts.select("price").where('price BETWEEN ? and ?', @max_pricerange1 + 0.01 , @max_pricerange2).count
@restricted_products_pricerange3 = filteredmatchingproducts.select("price").where('price BETWEEN ? and ?',  @max_pricerange2 + 0.01 , @max_pricerange3).count
@restricted_products_pricerange4 = filteredmatchingproducts.select("price").where('price BETWEEN ? and ?',  @max_pricerange3 + 0.01 , @max_pricerange4).count

最后,如果您真的需要计数和全文搜索,最好集成一个搜索引擎,如Sphinx或Solr。查看http://pat.github.io/thinking-sphinx/searching.html作为如何实现该参考的参考。

答案 1 :(得分:0)

product_name字段是什么?看起来你可以使用act_as_taggable gem(https://github.com/mbleigh/acts-as-taggable-on)。 LIKE语句导致数据库检查每个匹配的记录,并且它非常繁重。当你有500k记录时,它需要一段时间。

答案 2 :(得分:0)

如果您所处理的只是价格,那么您应该继续按照一系列价格进行操作,而不是ActiveRecord :: Relation。所以尝试类似的事情:

filteredmatchingproducts = (...).map(&:price)

然后对该阵列执行所有操作。此外,尝试尽可能批量加载大量请求,然后保留自己的计数等,如果可以的话。这样可以避免应用程序立刻咀嚼所有内存并减慢速度:

http://guides.rubyonrails.org/active_record_querying.html#retrieving-multiple-objects-in-batches

答案 3 :(得分:0)

执行这么多查询的原因是因为你要求它执行大量查询。 (此外,所有LIKE都会使事情变得缓慢。)这是您的代码,在每个查询之前添加注释(总共8个)。

filteredmatchingproducts = Allproduct.select("id, product_name, price")
.where('product_name LIKE ? 
OR (product_name LIKE ? AND product_name NOT LIKE ? AND product_name NOT LIKE ?       AND product_name NOT LIKE ? AND product_name NOT LIKE ? AND product_name NOT LIKE ?) 
OR product_name LIKE ? OR product_name LIKE ? OR product_name LIKE ? OR product_name LIKE ? OR (product_name LIKE ? AND product_name NOT LIKE ?) OR product_name LIKE ?', 
'%Bike Box', '%Bike Bag%', '%Pannier%', '%Shopper%', '%Shoulder%', '%Shopping%', '%Backpack%' , '%Wheel Bag%', '%Bike sack%', '%Wheel cover%', '%Wheel case%', '%Bike case%', '%Wahoo%', '%Bicycle Travel Case%')
.order('price ASC')

#!!!! this is a query "select ... offset x, limit y"
@selected_products = filteredmatchingproducts.paginate(:page => params[:page])  

#!!!! this is a query "select count ..."
@productsfound = filteredmatchingproducts.count
#!!!! this is a query "select ... order id asc, limit 1"
@min_price = filteredmatchingproducts.first
#!!!! this is a query "select ... order id desc, limit 1"
@max_price = filteredmatchingproducts.last

@price_range = @max_price.price - @min_price.price

@max_pricerange1 = @min_price.price + @price_range/4
@max_pricerange2 = @min_price.price + @price_range/2
@max_pricerange3 = @min_price.price + 3*@price_range/4
@max_pricerange4 = @max_price.price 

if @min_price == nil
#don't do anything - just avoid error
else

#!!!! this is a query "select ... where price BETWEEN X and Y"
@restricted_products_pricerange1 = filteredmatchingproducts.select("price").where('price BETWEEN ? and ?', 0 , @max_pricerange1).count
#!!!! this is a query "select ... where price BETWEEN X and Y"
@restricted_products_pricerange2 = filteredmatchingproducts.select("price").where('price BETWEEN ? and ?', @max_pricerange1 + 0.01 , @max_pricerange2).count
#!!!! this is a query "select ... where price BETWEEN X and Y"
@restricted_products_pricerange3 = filteredmatchingproducts.select("price").where('price BETWEEN ? and ?',  @max_pricerange2 + 0.01 , @max_pricerange3).count
#!!!! this is a query "select ... where price BETWEEN X and Y"
@restricted_products_pricerange4 = filteredmatchingproducts.select("price").where('price BETWEEN ? and ?',  @max_pricerange3 + 0.01 , @max_pricerange4).count
end