Rails 中 includes 跟 joins 的差別
Rails 中 includes
跟 joins
的差別
前言
最近幫忙在調校網站 n+1 的問題,所以想來寫個筆記,分成兩篇。
何謂 N+1
問題
在 Rails 中建立 model 的資料關聯十分的容易,但也是因為建立方便,所以很容易就會寫出效能差的程式碼,每一次進出 Database 都會消耗很大的效能,到底何謂 N+1
問題呢?
舉個例子:
1 | # User |
當我們想在畫面上印出所有 orders
的 user.email
,此時所造成的 query
就會長這樣:
上面的寫法就會先將所有 orders
,找出來,在針對每一筆的 order.user_id
去查找users
的表,共有 9 筆,所以找 9 次。
若有 n 筆就找 n 次,再加上一開始找出所有 orders
的 query
,就變成了 n+1
。
若資料量稍微龐大一點,效能就會變差。
請善用 includes
而 Rails 很貼心的有個語法可以讓我們避免這樣的問題,includes
。
若將 orders = Order.all
改成 orders = Order.all.includes(:user)
那在最一開始撈出所有 order 時,會順便一起把相對應的 user 找出來,此時繼續輸入:
1 | orders.each do |order| |
這時就不會進到 Database 中查詢。
從 n+1 變成 1+1 👍
includes
也支援巢狀關聯 (使用 hash)
如果除了 user
,還想一起找出跟 user
有關聯的 model 可以使用 hash 的寫法
1 | # 以 profile 為例 |
還有一個語法叫 joins
除了 includes
之外,還有一個很像的語法叫做 joins
,不過差別為 joins
是過濾 model
之間的關聯,像上方的寫法,在印出 user.email
的時候還是會產生查詢!
1 | orders = Order.all.joins(:user) |
而 has_many
又會跟 belongs_to
的情境有不一樣的回傳結果
1 | # User has_many :orders |
- 查詢所有具有
user_id
的order
,並回傳該order
的user
,若有很多order
所屬同一個user
,則回傳值會包含大量同一名user
的資料,可以用uniq
處理。
來個總結!
簡單來說,若是要處理大量資料,會比較建議使用 includes
,以避免 n+1 的問題,而 joins
的話,目前推論是需要對資料做關聯 model 的條件篩選,才會比較建議使用。