ActiveRecordのeager_loadとincludesとpreloadの違い (Rails)

August 30, 2022

確認環境

$ bundle exec ruby --version
ruby 2.7.5p203 (2021-11-24 revision f69aeb8314) [x86_64-darwin19]
$ bundle exec rails --version
Rails 6.0.4.6

使用するActive Recordのリレーション

class SampleUser < ApplicationRecord
  has_many :sample_user_items
end

class SampleUserItem < ApplicationRecord
  belongs_to :sample_user
end

eager_load

LEFT OUTER JOIN が使われます。

irb(main):013:0> SampleUserItem.eager_load(:sample_user)
  SQL (0.2ms)  SELECT "sample_user_items"."id" AS t0_r0, "sample_user_items"."sample_user_id" AS t0_r1, "sample_user_items"."price" AS t0_r2, "sample_user_items"."name" AS t0_r3, "sample_user_items"."created_at" AS t0_r4, "sample_user_items"."updated_at" AS t0_r5, "sample_users"."id" AS t1_r0, "sample_users"."name" AS t1_r1, "sample_users"."created_at" AS t1_r2, "sample_users"."updated_at" AS t1_r3 FROM "sample_user_items" LEFT OUTER JOIN "sample_users" ON "sample_users"."id" = "sample_user_items"."sample_user_id" LIMIT ?  [["LIMIT", 11]]

includes

結合先のテーブル (sample_users) で、絞り込まない場合、preload と同様、クエリが分割して発行されます。

irb(main):016:0> SampleUserItem.includes(:sample_user).where(sample_user: 1)
  SampleUserItem Load (0.2ms)  SELECT "sample_user_items".* FROM "sample_user_items" WHERE "sample_user_items"."sample_user_id" = ? LIMIT ?  [["sample_user_id", 1], ["LIMIT", 11]]
  SampleUser Load (0.1ms)  SELECT "sample_users".* FROM "sample_users" WHERE "sample_users"."id" = ?  [["id", 1]]

結合先のテーブル (sample_users) で、絞り込まない場合、eager_load と同様、LEFT OUTER JOIN が使われます。

irb(main):018:0> SampleUserItem.includes(:sample_user).where(sample_users: { id: 1 })
  SQL (0.3ms)  SELECT "sample_user_items"."id" AS t0_r0, "sample_user_items"."sample_user_id" AS t0_r1, "sample_user_items"."price" AS t0_r2, "sample_user_items"."name" AS t0_r3, "sample_user_items"."created_at" AS t0_r4, "sample_user_items"."updated_at" AS t0_r5, "sample_users"."id" AS t1_r0, "sample_users"."name" AS t1_r1, "sample_users"."created_at" AS t1_r2, "sample_users"."updated_at" AS t1_r3 FROM "sample_user_items" LEFT OUTER JOIN "sample_users" ON "sample_users"."id" = "sample_user_items"."sample_user_id" WHERE "sample_users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 11]]

preload

クエリが分割して発行されます。

irb(main):010:0> SampleUserItem.preload(:sample_user)
   (0.1ms)  SELECT sqlite_version(*)
  SampleUserItem Load (0.1ms)  SELECT "sample_user_items".* FROM "sample_user_items" LIMIT ?  [["LIMIT", 11]]
  SampleUser Load (0.1ms)  SELECT "sample_users".* FROM "sample_users" WHERE "sample_users"."id" = ?  [["id", 1]]

おわりに

includes は絞り込みの条件次第で、LEFT OUT JOIN したり、preload したりと挙動が変わるので、

個人的には使わない方がいいかなという気持ちになりました。

参考


SHARE

Profile picture

Written by tamesuu