Rails 的 N+1 queries
什麼是 N+1 queries 問題?
當我們在使用關聯資料時,可以很容易使用關聯的方式,去找我們用的資料,例如我們要取出每個 group 的leader的名字,會利用下述方法
@groups.each do |group|
group.leader.name
end
在沒有使用includes 的情況下,rails 會根據每個 group.id,去leader 的資料表中,尋找所需的資料的。假設有 10 筆group,rails 第一次會先去資料庫,取出全部的group(10筆),接著每使用一次group.leader,他就會去leader資料表中找,group_id 為 group.id,這樣 rails 總共去資料庫找了11 次資料,很影響效能。
若有 includes,rails會直接列出所有要去 leader 尋找的 group_id,然後一次取出所有的資料。
因此,原本 rails 要去資料庫11 次,才能取出所有資料,現在只需要2 次,就能將所需的資料全部取出,對於效能有很大的幫助。
如何確定解決了 N+1 queries ?
我們可以在 rails console 確定,是否解決 N+1 queries 問題了。


上面是很明顯的,有成功與沒成功的差別。
加入includes 的,只花了 0.4ms ,沒加入includes 的卻花了0.7ms ,省下了將近2倍的時間。
另外,實務上我們常常會需要更多關聯的資料,例如,我們除了要找這個留言是哪篇文章的,還要知道寫這篇文章的人是誰,使用 reply.post.user.name所以,我們會要使用巢狀的 includes 例如: Reply.includes(:post => [:user]),如此就能解決有多個關聯資料的 N+1 query 問題。
接下來,說明 Join 與 include 間的差別
Join 與 includes 最大的差別是,join 不像 includes 會將關聯的資料取出,所以通常是用於查找資料的時候。而 join 用於belongs_to 與 has_many 時,也有所不同:
Belongs_to 的時候
例如 : Post.joins(:user),Join 這樣會回傳所有含有 user_id 的 Post,而因為 Join 不會去查看關聯的資料表(user 的 table ),所以當我們使用 post.user.name 時,他還是得去資料庫找一次資料。
has_many 的時候
users = User.joins(:posts),Join會去找出全部含有 user_id 的post,然後回傳他的 user。這裡是回傳所有post中,對應的user,所以如果一個user1同時擁有多篇post的話,會回傳多個 user1。使用上,可以多加個 uniq ,濾掉不必要的user。
因此,join 通常是用於查詢資料,且不需要知道關聯資料的任何資訊的時候。
最後,記得在使用關聯資料的時候,要確定有沒有N+1 queries 的問題!