0
Help us understand the problem. What are the problem?

posted at

【個人メモ】N+1問題 preload、eager_loadのまとめ

N+1の対策メソッド

  1. joins
  2. left_outer_joins
  3. eager_load
  4. preload
  5. includes

eager_load、preload、includesは、一度取得したデータをキャッシュする。2回目以降は、DBではなく、メモリ上にキャッシされたデータを取得することになる。
キャッシュが不要な場合は、joins或いはleft_outer_joinsを使用する。これらはSQLの句なので、この記事で詳細は触れない。

N+1問題とは?

ループ処理の中で都度SQLを発行してしまう問題のこと。
【Ruby on Rails】N+1問題ってなんだ?

preload

joinは行われず、2つのテーブに対してそれぞれSQLを発行する。(=2回SQLを発行)
joinを行わないため、where句による絞り込みが出来ない。
レコード量が多い場合、joinの処理を行うeager_loadはパフォーマンスや負荷に影響する場合がある。
where句による絞り込みが不要な場合は、preloadを優先的に使用する。

 <% @articles.each do |article| %>
   <% article.tags.each do |tag| %>
     <%= tag.name %>
   <% end %>
 <% end %>
 def index
   @user = User.find(1)
   - @articles = @user.articles #修正前
   + @articles = @user.articles.preload(:tags) #修正後
 end

修正前

  Tag Load (0.1ms)  SELECT "tags".* FROM "tags" INNER JOIN "article_tags" ON "tags"."id" = "article_tags"."tag_id" WHERE "article_tags"."article_id" = ?  [["article_id", 698]]
   app/views/profiles/_articles.html.erb:17
  Tag Load (0.1ms)  SELECT "tags".* FROM "tags" INNER JOIN "article_tags" ON "tags"."id" = "article_tags"."tag_id" WHERE "article_tags"."article_id" = ?  [["article_id", 699]]
   app/views/profiles/_articles.html.erb:17
  Tag Load (0.1ms)  SELECT "tags".* FROM "tags" INNER JOIN "article_tags" ON "tags"."id" = "article_tags"."tag_id" WHERE "article_tags"."article_id" = ?  [["article_id", 700]]

修正後

 Tag Load (8.9ms)  SELECT "tags".* FROM "tags" WHERE "tags"."id" IN (1718, 3985, 937, 1418, 4902, 4760, 2461, 641, 2236, 4994, 4474, 4190, ・・・

eager_load

ネストした関連モデルのデータを一括取得する場合にも使用する。ネストした関連テーブとは、1対多対多(或いは多対多対多など)のような関連付けのテーブルのこと。
下記は単に関連テーブルの取得。

 <% @skill_categories.each do |category| %>
    <% category.skills.each do |skill| %>
      <%= skill.name %>
    <% end %>
 <% end %>
 def user_reccomend_skill_categories
  - @user.skills.map(&:skill_category).
      filter { |skill_category| skill_category.reccomend }.uniq

  + SkillCategory.eager_load(:skills).
      where(reccomend: true).
      where(skills: { user_id: @user.id })
 end
 Skill Load (0.1ms)  SELECT "skills".* FROM "skills" WHERE "skills"."skill_category_id" = ?  [["skill_category_id", 929]]
   app/views/profiles/_user.html.erb:42
  Skill Load (0.1ms)  SELECT "skills".* FROM "skills" WHERE "skills"."skill_category_id" = ?  [["skill_category_id", 896]]
   app/views/profiles/_user.html.erb:42
  Skill Load (0.1ms)  SELECT "skills".* FROM "skills" WHERE "skills"."skill_category_id" = ?  [["skill_category_id", 1265]]
   app/views/profiles/_user.html.erb:42
 SQL (7.4ms)  SELECT "skill_categories"."id" AS t0_r0, "skill_categories"."name" AS t0_r1, "skill_categories"."reccomend" AS t0_r2, "skill_categories"."created_at" AS t0_r3, "skill_categories"."updated_at" AS t0_r4, "skills"."id" AS t1_r0, "skills"."name" AS t1_r1, "skills"."user_id" AS t1_r2, "skills"."skill_category_id" AS t1_r3, "skills"."created_at" AS t1_r4, "skills"."updated_at" AS t1_r5 FROM "skill_categories" LEFT OUTER JOIN "skills" ON "skills"."skill_category_id" = "skill_categories"."id" WHERE "skill_categories"."reccomend" = ? AND "skills"."user_id" = ?  [["reccomend", 1], ["user_id", 1]]

includes

状況に応じて、よしなにpreload、eager_loadで振る舞う。どちらを意図しているのか、コード上で分かりづらい為非推奨。

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
0
Help us understand the problem. What are the problem?