如何优化数据库查询

时间:2015-10-19 18:49:56

标签: ruby-on-rails performance

我有两种情况需要很长时间才能加载页面,而且我不知道如何在最近开始使用RoR时更快地调用数据库。

案例A

我正在尝试显示根类别(使用祖先gem)和与这些根类别相关联的供应商数量。共有15个根类别。

结果如下:

  • 时尚(14)
  • 自动(26)
  • ...

suppliers.html.erb

<% Category.roots.each do |category| %>
    <li class="pointer txt-hover-grey mili-margin-bottom">
        <span class="micro-margin-right">
            <%= link_to category.name, category_suppliers_path(category), :title => category.name %>
        </span>
        <span class="txt-alt">
            (<%= category.suppliers_count_active %>)
        </span>
    </li>
<% end %>

category.rb

def suppliers_count_active
    self.suppliers.where(:active => true).pluck(:id).count
end

案例B(祖先宝石)

这与主要类别菜单有关(就像您可以在任何电子商店中找到的那样)。有15个根类别(级别0),然后每个根类别有3个子类别(总共45个),每个子类别有大约5个子子类别(总共大约225个)。对于每个子子类别,我也填充该类别中的产品数量。

结果如下:

  • 时尚

    • 男装
      • T恤(34555)
      • 内衣(14555)
      • ...
    • 女性
      • T恤(43000)
      • 内衣(23000)
  • 运动

    • 滑雪板
      • XYT(2323)
      • ...
    • ...
  • ...

categories_menu.html.erb

<div class="content no-padding padding-static relative table menu-nr">
 <!--   root_categories -->
<% Category.includes(:image, :products).serializable.each do |root| %>
    <div class="table-center container-center full-h categories-trigger">
        <%= link_to Category.find(root['id']).category_link, :title => root['name'] do %>                
            <div class="uppercase full-h size-tiny semi-bold txt-hover-main_light semi-padding-top semi-padding-bottom">
            <%= root['name'] %>
            </div>
        <% end %>
        <div class="categories-hide categories-dropdown shadow z-1000 bg-white txt-black size-tiny">
            <div class="table full-w inwrap-dropdown">
                <div class="cell">
                    <div class="table dropdown-left">
                    <% 
                        children_sorted = root['children'].sort_by { |child| child['products_sum_count'] }.reverse!.first(3)
                        children_sorted.each do |cat| %>
                        <div class="cell container-left">
                            <div class="table">
                                <div class="cell container-top">
                                    <div class="mili-margin-left mili-margin-right">
                                    <% cat2 = Category.find_by(:id => cat['id'])
                                    if !cat2.image.blank? %>
                                        <%= image_tag(cat2.image.image.url(:small), :title => cat2.image.title, :alt => cat2.image.title, :class => "img-category") %> 
                                        <% end %>    
                                    </div>
                                </div>
                                <div class="cell">
                                    <h5 class="mili-margin-bottom">
                                        <%= link_to "#{cat['name']}", Category.find(cat['id']).category_link, :title => cat['name'] %>
                                    </h5>
                                    <div class="txt-grey_dark semi_bold mili-margin-bottom">
                                    <% 
                                       # cat_children = cat.children.includes(:products)
                                        cat['children'].first(7).each do |sub_cat| 
                                    %>  
                                        <%= link_to Category.find(sub_cat['id']).category_link, :title => sub_cat['name'], :class => "block txt-hover-grey micro-margin-bottom" do %>
                                            <%= "#{sub_cat['name']}" %> <span class="txt-alt"><%= "(#{sub_cat['products_sum_count']})" %></span>
                                        <% end %>
                                    <% end %>
                                    </div>

                                    <%= link_to "Další kategorie >", Category.find(cat['id']).category_link, :class => "semi-margin-top block txt-alt txt-hover-alt_dark" %>
                                </div>
                            </div>
                        </div>
                    <% end %>
                    </div>
                </div>
                <div class="cell bg-grey_light semi-padding-left">
                    <div class="table">
                        <div class="cell container-left">
                            <div class="mili-margin-left mili-margin-right">
                                <h5 class="txt-alt mili-margin-bottom">
                                    <%= t(:menu_suppliers_title) %>
                                </h5>

                                <% 
                                suppliers = Supplier.joins(:categories).where(:active => true, categories: { :id => root['id'] }).last(3)
                                suppliers.each do |supplier| 
                                %>
                                <% cache supplier do %>
                                <div class="table relative mili-margin-bottom">
                                    <div class="cell inline relative wrap-shop">
                                        <div class="absolute-center-nr center inwrap-shop inwrap-shop-rohlik btn border">
                                            <%= link_to supplier_path(supplier), :title => "#{t(:menu_suppliers_link_title)} #{supplier.name}" do %>
                                                <%= image_tag(supplier.image.image.url(:small), :alt => supplier.image.title) if !supplier.image.blank? %>
                                            <% end %>
                                        </div>
                                    </div>
                                    <div class="col inline semi-margin-left-nr">
                                        <div class="table txt-avatar-small full-w">
                                            <div class="table-center">
                                                <%= link_to supplier.name, supplier_path(supplier), :title => "#{t(:menu_suppliers_link_title)} #{supplier.name}", :class => "semi-bold block micro-margin-bottom" %>
                                                <div class="txt-alt">
                                                    <%= t(:homepage_suppliers_logo_text, :commission => supplier.commission_donated, :commission_type => supplier.commission_type ) %>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                                <% end %>
                                <% end %>
                                <span class="block txt-alt txt-hover-alt_dark half-margin-bottom">
                                </span>
                                <%#= link_to t(:menu_suppliers_link_others), category_suppliers_path(:id => root['id']), :title => t(:menu_suppliers_link_others), :class => "block txt-alt txt-hover-alt_dark half-margin-bottom" %>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div class="categories-hide wrap-categories-arrow relative">
            <div class="categories-arrow absolute z-1000">
            </div>
        </div>
    </div>
<% end %>
</div>

category.rb

def self.serializable
  Category.includes(:translations).where(categories: {:active => true, :ancestry_depth => 0..2 }, category_translations: {:locale => I18n.locale.to_s} ).arrange_serializable(:order => 'category_translations.name') 
end

def category_link
   category_path(self)
end

这两种情况都需要几秒钟才能加载。任何建议真的很感激。

谢谢你, 米罗

更新1:

在这里您可以看到NewRelic的输出。在尝试实现dalli memcache和identity_cache之后,它与案例B有关。我还上传了菜单的屏幕截图。

更新2:

最耗时的部分似乎是以下代码:

result = Benchmark.ms { Category.includes(:translations).where(categories: {:active => true, :ancestry_depth => 0..2 }, category_translations: {:locale => I18n.locale.to_s} ).arrange_serializable(:order => 'category_translations.name') }
=> 7207.116272300482

它在层次结构中生成所有活动类别(大约1000)的哈希值,因此我可以为菜单正确呈现它。

不确定如何优化此部分。

更新3

我使用postgres数据库。

类别翻译表

create_table "category_translations", force: :cascade do |t|
  t.integer  "category_id", null: false
  t.string   "locale",      null: false
  t.datetime "created_at",  null: false
  t.datetime "updated_at",  null: false
  t.string   "name"
end

add_index "category_translations", ["category_id"], name: "index_category_translations_on_category_id", using: :btree
add_index "category_translations", ["locale"], name: "index_category_translations_on_locale", using: :btree

类别表

create_table "categories", force: :cascade do |t|
  t.string   "name"
  t.boolean  "active",             default: false
  t.integer  "level"
  t.datetime "created_at",                         null: false
  t.datetime "updated_at",                         null: false
  t.string   "ancestry"
  t.string   "mapping"
  t.integer  "products_count",     default: 0,     null: false
  t.integer  "products_sum_count", default: 0
  t.integer  "ancestry_depth",     default: 0
  t.string   "category_link"
  t.string   "image_link"
end

add_index "categories", ["ancestry"], name: "index_categories_on_ancestry", using: :btree

5 个答案:

答案 0 :(得分:1)

案例A

此处最好的办法是在Category模型中缓存活跃供应商的计数数量。

  • active_supliers_count模型
  • 中添加Category
  • 向供应商模型添加after_save挂钩,当它变为活动/非活动时,递增/递减类别计数器

案例B

此处的问题与案例A中的问题相同,您需要添加缓存计数器。

似乎你在循环中创建了多个包含。你只能制作一件所需的东西。

例如

Category.includes(children: [:image, :products])

答案 1 :(得分:0)

是否有理由需要pluck

self.suppliers.where(:active => true).pluck(:id).count

使用self.suppliers.where(:active => true).count可能会更快,因为pluck返回每个id的数组,听起来你只需要在那里计数。

答案 2 :(得分:0)

您的查询显然是资源密集型的。索引使它们非常快。你使用的是哪个数据库?你的桌面结构是什么样的?您是否在翻译表中索引了category_id(外键属性应始终编入索引)。 哪种类型的列是区域设置?它是一个字符串?默认情况下,字符串由rails存储为255 varchar,mysql2可以索引的最大值为varchar,长度为190。因此,要纠正您可以在迁移中使用限制,因为我不认为区域设置需要大量空间。然后,您就可以将其编入索引。这将使您的查询非常快。

答案 3 :(得分:0)

案例A

您正在查询每个类别的供应商数量。您可以像Nikita建议的那样缓存类别的数量,或者如何使用ruby group_by获取控制器中每个类别的计数?

active_suppliers = Suppliers.where(:active => true).select('id, category_id')
@category_suppliers = active_suppliers.group_by(&:category_id)
# =>  {2 =>[#<Supplier id: 4, category_id: 2>, ...], 3 => ...}

(您希望在模型方法中使用此方法)。然后在您的视图中,使用该哈希执行以下操作:

<span class="txt-alt">
  (<%= @category_suppliers[category.id].length %>)
</span>

案例B

我没有使用过祖先的宝石,但是从我读到的它试图在一个胖查询中拉出你的相关对象,是吗?我认为一个重要问题是序列化器。当使用arrange_serializable时,我猜测gem必须遍历整个结构才能序列化它,但是然后你继续前进并再次访问数据的每个节点以呈现你的页面。

为什么不代替:

<% children_sorted = root['children'].sort_by { |child| child['products_sum_count'] }.reverse!.first(3)

你这样做:

<% children_sorted = root.children.sort_by { |child| child['products_sum_count'] }.reverse!.first(3)

另外,关于为路径助手实例化对象的另一件事。你做了很多:

<%= link_to Category.find(sub_cat['id']).category_link, ...%>

但既然你已经拥有了id,为什么不用这样的帮助器来构建呢?

<%= link_to category_path(sub_cat['id']), ...%>

查询数据库和实例每个类别仅仅是为了构建'categories /:id'类型的链接,这似乎是浪费资源?或者这个链接更复杂,需要更多数据?如果是这样,为什么不为它使用你在那时使用的数据而不查询数据库来构建一个帮助器呢?

注意: 修复速度问题后,您应该真正解决此代码的可维护性问题。如果您以外的其他人必须找到并修改通过子产品计数对类别进行排序的特定代码段,(...root['children'].sort_by { |child| child['products_sum_count'] }.reverse!.first(3))如果它是具有描述性名称的模型/类方法,那会不会更容易,你可以单元测试,而不是在那里与视图混合?还要考虑将视图代码的逻辑部分提取为将变量传递给的部分。

答案 4 :(得分:0)

<案例A

这里最好的办法是在类别模型中缓存活跃供应商的计数数量。

<案例B

似乎你在循环中创建了多个包含。你可以只使用所需的一切。