過去POST
過去自分がTECH::CAMPのメンターをしていた時期にメモしていた内容を公開します。
N+1問題が理解できない。includes(:user)とする意味がわからない。
class TweetsController < ApplicationController
# 中略
def show
@tweet = Tweet.find(params[:id])
@comments = @tweet.comments.includes(:user)
end
# 中略
end
で使用している。
これはなにも考えなければ
@comments = @tweet.comments.includes(:user)
の部分は
@comments = @tweet.comments
でもできる。
しかしこれではN+1問題が発生してしまう。
(要は、SQLの発行回数が多くなってしまう。)
【問題の概要】
この文の意味は
詳細を表示したツイートについているコメントのレコードを取得
コメントのレコードに紐つくユーザーレコードの取得
をしている
単純に
@tweet.comments
のように書くと、コメントレコードごとにユーザーレコードを取ってくるという動作をしてしまう。
つまりSQL文でかくと、
(tweetは一旦省略、コメント数は4件、ユーザーは2人とする。)
SELECT 'comments'.* FROM 'comments’
を実行すると
SELECT 'users'.* FROM 'users' WHERE 'users'.'id' = 1 LIMIT 1
SELECT 'users'.* FROM 'users' WHERE 'users'.'id' = 2 LIMIT 1
SELECT 'users'.* FROM 'users' WHERE 'users'.'id' = 2 LIMIT 1
SELECT 'users'.* FROM 'users' WHERE 'users'.'id' = 2 LIMIT 1
のようにcommentsレコードの数だけ、userレコードを取得するクエリが発行される。
これがN+1問題。
では.includes(:user)
をつかうとどうなるのか
SELECT 'comments'.* FROM 'comments'
SELECT 'users'.* FROM 'users' WHERE 'users'.'id' IN (1, 2)
のように一文で終わる。
これがincludes
の意味です。
(:user)
はuser_id
ですね。