[英]How can I speed up this query in a Rails app?
我需要帮助优化Rails 5 app
的一系列查询。 以下内容说明了我在做什么,但是如果不清楚,请告诉我,我将尝试进一步详细介绍。
我的模型中有以下方法:
在我的IncomeReport
模型中:
class IncomeReport < ApplicationRecord
def self.net_incomes_2015_totals_collection
all.map(&:net_incomes_2015).compact
end
def net_incomes_2015
(incomes) - producer.expenses_2015
end
def incomes
total_yield * 1.15
end
end
在Producer
模型中,我具有以下内容:
class Producer < ApplicationRecord
def expenses_2015
expenses.sum(&:expense_per_ha)
end
end
在Expense
模型中,我有:
class Expense < ApplicationRecord
def expense_per_ha
total_cost / area
end
end
在控制器中,我有这个功能(我正在使用一个称为descriptive_statistics的gem来获取最小值,最大值,四分位数等,以防您最后想知道那部分)
@income_reports_2015 = IncomeReport.net_incomes_2015_totals_collection.extend(DescriptiveStatistics)
然后我认为
<%= @income_reports_2015.descriptive_statistics[:min] %>
当数据库中只有几个对象时,此代码有效。 但是,由于现在有成千上万的查询,所以要花很多时间才能得出结果。 它花了很长时间才超时!
我该如何优化以获得最佳效果?
一种方法可能是以不同的方式构建应用程序。 我认为在这种情况下可能会使用面向服务的体系结构 。
您可能不希望在用户进入该视图时进行查询,而希望使用辅助程序间歇性地查询,然后写入CSV。 因此,用户导航到该视图,您可以改为从CSV中读取内容。 这样运行起来会更快,因为您无需再在其中进行查询(当用户导航到此页面时),而是从之前作为后台进程创建的文件中读取内容。
显然,这有其自身的挑战,但是我过去已经这样做以解决类似的问题。 我编写了一个应用程序,每分钟一次从10个不同的外部API提取数据。 10次不同的访存导致数据库中有10个对象。 每天DB中10 * 60 * 24 = 14,400条记录。 当用户加载需要此数据的页面时,他们将加载7天的记录,100,800个数据库行。 我遇到了同样的问题,在运行时执行查询会导致超时,我写了一个CSV并作为解决方法将其读取。
IncomeReport
的结构是IncomeReport
? 通过查看代码的问题在于all
的net_incomes_2015_totals_collection
。 all
命中数据库并返回所有记录,然后将它们映射。 过度杀伤力。 尝试过滤数据,减少查询,减少选择并直接通过ActiveRecord获得所需的所有信息。 Ruby循环会减慢速度。
因此,在不知道表结构及其数据的情况下,我将执行以下操作:
def self.net_incomes_2015_totals_collection
where(created_at: 2015_start_of_year..2015_end_of_year).where.not(net_incomes_2015: nil).pluck(:net_incomes_2015)
end
另外,我将确保为created_at和net_incomes_2015提供一个复合索引。
它可能会很慢,但要比现在好。 您应该考虑在午夜在后台聚合数据(resque,sidekiq等)(并将其缓存吗?)。
希望能帮助到你。
看来您这里有几个n + 1个查询。 每个报告在一个单独的查询中获取其生产者。 然后,每个生产者在不同的查询中获取其每个费用。
您可以通过抛出preload(:producer)
而不是all
来避免出现第一个问题。 然而, sum
晚节将很难避免,因为sum
会自动闪光的查询。
您可以通过以下方法避免该问题
def self.net_incomes_2015_totals_collection
joins(producer: :expenses).
select(:id, 'income_reports.total_yield * 1.15 - SUM(expenses.total_cost/expenses.area) AS net_incomes_2015').
group(:id).
map(&:net_incomes_2015).
compact
end
在一个查询中获取所有内容。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.