0%

N + 1 query

當我們在rails專案裡面執行關聯性的查詢時,時常會出現N+1 query的狀況,造成效能浪費。舉個例子,在一些網站上我們常可以看到使用者(user)可以針對文章(post)有多個評論(comment),而我們想在頁面上列出所有comment以及每個comment的user是誰時,就會出現N + 1 query:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#user model
class User < ApplicationRecord
has_many :comments
end

#comment model
class Comment < ApplicationRecord
belongs_to :user
end

#post model
class Post < ApplicationRecord
has_many :comments
end

#posts_controller
def show
@comment = Comment.new
@comments = @post.comments
end

#post的show頁面,列出此post所有comments,並且標示user
<ul>
<% @comments.each do |comment| %>
<li>
[<%= comment.user.nickname %>]
<%= comment.content %>
</li>
</ul>

只要每次我們刷新show頁面時,除了撈出所有comments以外,還會針對每一個comment去跑迴圈,到資料庫撈它的user,而所謂的N指的就是這個動作。

使用includes方法解決(eager loading)

1
2
3
4
5
6
7
#posts_controller
def show
@comment = Comment.new
@comments = @post.comments.includes(:user)
end

#查詢post的所有comment時,把user資料一併關聯進來,就可以先分析並一次把user資料撈出來。

從畫面可以看到從原本的N + 1的查詢,變成1 + 1了,而SQL語法則使用IN()取代原本一筆筆用=去取資料,一次所有相關user資料撈出來。

網路上其實也有人針對rails的N + 1問題做成套件,當發生此問題就會跳出提醒,相關資訊和安裝方式可參考:https://github.com/flyerhzm/bullet