[英]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.