如何(大规模)减少Rails应用程序中的SQL查询数量?

在我的Rails应用程序中,我的users可以拥有许多invoices ,而这些invoices又可以payments很多payments

现在,在dashboard视图中,我想总结user收到的所有payments ,按年,季度或月份订购。 payments还细分为总额净额税额

user.rb

 class User  :items).all payments_with_invoice.select { |x| range.cover? x.date }.sum(&:"#{kind}_amount") end end 

invoice.rb

 class Invoice < ActiveRecord::Base belongs_to :user has_many :items has_many :payments def total items.sum(&:total) end def subtotal items.sum(&:subtotal) end def total_tax items.sum(&:total_tax) end end 

payment.rb

 class Payment < ActiveRecord::Base belongs_to :user belongs_to :invoice def percent_of_invoice_total (100 / (invoice.total / amount.to_d)).abs.round(2) end def net_amount invoice.subtotal * percent_of_invoice_total / 100 end def taxable_amount invoice.total_tax * percent_of_invoice_total / 100 end def gross_amount invoice.total * percent_of_invoice_total / 100 end end 

dashboards_controller

 class DashboardsController < ApplicationController def index if %w[year quarter month].include?(params[:by]) range = params[:by] else range = "year" end @ranges = @user.send("#{range}_ranges") end end 

index.html.erb

   'range', :object => range %>  

_range.html.erb

    

现在的问题是这种方法有效,但也产生了大量的SQL查询。 在典型的dashboard视图中,我获得了100多个 SQL查询。 在添加.includes(:invoice)还有更多查询。

我假设一个主要问题是每个发票的subtotaltotal_taxtotal都没有存储在数据库的任何地方,而是根据每个请求进行计算。

谁能告诉我如何在这里加快速度? 我不太熟悉SQL和ActiveRecord的内部工作,所以这可能是问题所在。

谢谢你的帮助。

每当revenue_between ,它就会在给定的时间范围内获取payments ,并从db中获取相关的invoicesitems 。 由于时间范围有很多重叠(月,季,年),因此一遍又一遍地提取相同的记录。

我认为最好一次获取用户的所有付款,然后在Ruby中过滤和汇总它们。

要实现,请更改revenue_between方法,如下所示:

 def revenue_between(range, kind) #store the all the payments as instance variable to avoid duplicate queries @payments_with_invoice ||= payments.includes(:invoice => :items).all @payments_with_invoice.select{|x| range.cover? x.created_at}.sum(&:"#{kind}_amount") end 

这将急切加载所有付款以及相关的发票和物品。

同时更改invoice汇总方法,以便它使用预先加载的items

 class Invoice < ActiveRecord::Base def total items.map(&:total).sum end def subtotal items.map(&:subtotal).sum end def total_tax items.map(&:total_tax).sum end end 

除了@tihom提出的memoizing策略之外,我建议你看一下Bullet gem ,正如他们在描述中所说,它将帮助你杀死N + 1个查询和未使用的急切加载。

您的大多数数据不需要是实时的。 您可以使用计算统计数据的服务并将其存储在任何位置(Redis,缓存…)。 然后每10分钟或根据用户的要求刷新它们。

首先,渲染没有统计数据的页面并使用ajax加载它们。