Ruby on Rails 該如何避免 N+1 問題

CK Yang
Oct 26, 2020

因為 Ruby on Rails 是透過 ORM (Object Relational Mapping) 的特性所以可以方便且輕易的使用後端語言去操作資料庫,這時注意出現所謂的 N + 1 !

什麼是 N + 1 ?

這是新手非常採到的一個坑,如果以現實生活舉例的話,我們到便利商店買東西,我們一次就只買一樣東西去結帳重複好幾次開好幾張發票,也就是 N 的部分 ,外加我們進去便利商店的 1 次 ,但其實可以一次買完一次結帳為什麼不這樣做呢? (( 因為要很多發票~誤XD

我們先來看以下程式碼有一個animal的model它只屬於shelter,而shelter han_many animals

可以想像如果今天想要看某個 shelter 裡的所有 animal 並且只要 name 欄位可以這樣寫

## animal.rb
class Animal < ApplicationRecord
belongs_to :shelter
end
## shelter.rb
class Shelter < ApplicationRecord
has_many :animals
end
## shelters_controller.rb
def index
@shelters = Shelter.all
end
## shelters/index.html.erb
<% @shelters.each do |shelter| %>
<%= shelter.animals.map(&:name) %>
<% end %>

如果看log的話會發現除了因為有三個 shelter 的緣故 各撈了一次資料之外,還有找到自己本身的一次也就是 3 + 1 次,總共查詢了 4 次 !

那麼該如何解決 N + 1 呢?

最簡單的方式是使用 includes 就可以避免! 因為 includes 會幫我們將有關連的 table 一併撈出。

## shelters_controller.rb
def index
@shelters = Shelter.all.includes(:animals)
end
## shelters/index.html.erb
<% @shelters.each do |shelter| %>
<%= shelter.animals.map(&:name) %>
<% end %>

這時再來看 log 會發現除了查詢自己本身一次之外,我們只用了一次查詢就撈出所有 shelter 關聯的 animals 的 table,就避免掉了 N + 1 的問題摟 !

總結

如果不解決 N + 1 的問題,可能會造成效能上的問題,想一想如果今天有一萬筆甚至十萬筆的資料都要各別找一次資料庫多麼累人! 當然還有其他解法今天就先以最基礎的方式作為解決方案!

--

--