Rails 的 N+1 queries

什麼是 N+1 queries 問題?

Huang Yu Ti
Sep 2, 2018 · 3 min read

當我們在使用關聯資料時,可以很容易使用關聯的方式,去找我們用的資料,例如我們要取出每個 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 的問題!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade