簡潔に、どんな問題なの??
情報をDBから抽出する際に生じる問題で、
全レコードの取得に一回 + 各レコードの取得分にN回、 それぞれSQLを発行してしまっている状況 を指します。
⬇️SQL文で実際に見てみると分かりやすいかもしれません。⬇️
SELECT 'phones'.* FROM 'phones' # Phone.allの的なコードを想定。
SELECT 'users'.* FROM 'users' WHERE 'users'.'user_id' = 1
SELECT 'users'.* FROM 'users' WHERE 'users'.'user_id' = 2
SELECT 'users'.* FROM 'users' WHERE 'users'.'user_id' = 3
SELECT 'users'.* FROM 'users' WHERE 'users'.'user_id' = 4
SELECT 'users'.* FROM 'users' WHERE 'users'.'user_id' = 5
: : :
: : :
: : :
まず結論をお伝えするためにcontrollerやModelの定義を省きましたが、
**「一人のUserが一台の電話(phone)を所有している関係(一対一の関係性)における、情報の一覧取得」**を想定しています。
恥ずかしながら初学者の私は、⬆️のようなSQLを見ても「どこがN+1なんだ??」と疑問に思いました。笑
そこで上記SQL文を少し分割して見てみると、「N+1問題」と呼ばれる訳が腹落ちするようになります。
SELECT ‘phones’.* FROM ‘phones’ # phone.all の実行
SELECT 'users'.* FROM 'users' WHERE 'users'.'user_id' = 1
SELECT 'users'.* FROM 'users' WHERE 'users'.'user_id' = 2
SELECT 'users'.* FROM 'users' WHERE 'users'.'user_id' = 3
SELECT 'users'.* FROM 'users' WHERE 'users'.'user_id' = 4
SELECT 'users'.* FROM 'users' WHERE 'users'.'user_id' = 5
: : :
: : :
: : :
一覧に表示するデータを取得するために SELECT を 1 回実行
各データの関連データを取得するために SELECT を N 回実行
つまり、DBへのアクセス(SELECT)が合計 N+1 回実行されてしまうという状況です。
SQL文の発行順通りに解釈するならば、N+1問題というよりも***「1+N問題」***と捉えた方が分かりやすそうですね。
いずれにせよ膨大なDBを利用する場合は、『N』にあたる部分の行数が何万行にも及ぶため、大きな問題となりそうです...!!
#じゃあどうやって解決するの??
順序が逆になりましたが、
###上記N+1問題が発生しているコード(例)は以下のようなものです。
はじめに、PhoneテーブルとUserテーブルは下記によって一対一の関係性で結合しています。
has_one :user
次に、phone_controllerでのuserテーブルのカラムの呼び出しについて、
例えば、phone_idが1の予約者を@userに代入したいとすると、
@phone = phone.find_by(phone_id: 1)
@user = @phone.user.user_name
上記のようになります。
そして実際にphoneのviewで予約者一覧を表示したいときには、「each do」文がまず思い浮かぶのではないでしょうか。
@phones = phone.all
@phones.each do |@phone|
@user = @phone.user.user_name
@user
end
これにて一覧の取得は可能となるはずですが、上述の通りこれではN+1問題が発生してしまうのです。
###includesメソッドによる解決方法
includesメソッドは、関連している複数のテーブルからデータを取得してくるときのアクセス回数を大きく減らすことができるメソッドです。
@phones = phone.all.includes(:user)
上記のように記述する事で、DBへのアクセス回数が1+N行に及んだSQLが下記のようになります。
SELECT ‘phone’.* FROM ‘phones’
SELECT 'users'.* FROM 'users' WHERE 'users'.'user_id' IN (1,2,3,4,5)
includesメソッドによってSQLの発行がわずか二行に収まり、N+1問題を解決する事ができました!