N+1問題について

要約

関連テーブルからデータを取得するときに、結合せずに、1つづつデータを取ると、負荷がかかる。

1つづつデータを取る場合とは、まず親となるテーブルから対象のデータ(N個)を取得するSQLが1回、 子テーブルの外部キーを条件にデータを取得するSQLがN回、合計1+N回SQLを発行する事になる。 このやり方だと負担がかかる。 対応策は必要なレコードはまとめて取得することを意識する。

データを取得する状況を考える

例えば、CompaniesテーブルとUsersテーブルがあって、 CompaniesはたくさんのUserを持っている状態とする。 各Companiesに紐づくUserのデータを表示したい場合を考える。

N+1の取り方

Comapaniesを取ってくる

Comapany.all

次に取ってきたCompaniesをeachで回してUsersの外部キーを条件に名前を取得する

Company.all.each do |c|
    puts User.find_by(company_id:c.id)
end

そうすると以下のようにSQLが複数発行される

# usersテーブルから取得(1回目)
SELECT  `users`.* FROM `users` WHERE `users`.id = 1 LIMIT 1
# usersテーブルから取得(2回目)
SELECT  `users`.* FROM `users` WHERE `users`.id = 2 LIMIT 1
# usersテーブルから取得(3回目)
SELECT  `users`.* FROM `users` WHERE `users`.id = 3 LIMIT 1

全体のCompanyが1回、NこのCompanyでN回、合計1+N回SQLが発行される。

対応策

テーブルを結合させて、まとめて取得する

Rails における内部結合、外部結合まとめ - Qiita

例えば、joinsを使うと以下のようになる。

Comapny.joins(:user)