Railsのeager load

RailsではN + 1問題によるパフォーマンス悪化を避けるために、
ActiveRecordのeager loadingを利用して先に関連先のデータを取得することをよく行います。

一般的には、includesを指定することが多いですが、preload、eager_loadというメソッドも用意されているので調べてみました。

確認環境

Rails 4.2.1

preload

  • preloadを使うとSQLが2本発行される
Blog.preload(:posts)

Blog Load (42.1ms)  SELECT `blogs`.* FROM `blogs`
Posts Load (22.7ms)  SELECT `posts`.* FROM `posts` WHERE `posts`.`blog_id` IN (1, 2, 3, 4, 5)
  • 関連先のカラムの条件を指定できない
Blog.preload(:posts).where('posts.name = "ABC"')

blog Load (1.3ms)  SELECT `blogs`.* FROM `blogs` WHERE (posts.name = "ABC")
Mysql2::Error: Unknown column 'posts.name' in 'where clause': SELECT `blogs`.* FROM `blogs` WHERE (posts.name = "ABC")

includes

  • preloadと同じ動作。SQLが2本発行される
Blog.includes(:posts)

Blog Load (42.1ms)  SELECT `blogs`.* FROM `blogs`
Posts Load (22.7ms)  SELECT `posts`.* FROM `posts` WHERE `posts`.`blog_id` IN (1, 2, 3, 4, 5)
  • 関連先のカラムの条件を指定する場合は、条件をhashで指定
    条件によってはSQLをひとつにまとめて発行する
Blog.includes(:posts).where(posts: {name: 'ABC'})

SQL (0.5ms)  SELECT `blogs`.`id` AS t0_r0, `blogs`.`created_at` AS t0_r1, `blogs`.`updated_at` AS t0_r2, `posts`.`id` AS t1_r0, `posts`.`blog_id` AS t1_r1, `posts`.`name` AS t1_r2 `posts`.`created_at` AS t1_r3, `posts`.`updated_at` AS t1_r4 FROM `blogs` LEFT OUTER JOIN `posts` ON `posts`.`blog_id` = `blogs`.`id` WHERE `posts`.`name` = 'ABC'
  • 関連先のカラムの条件を指定する場合は、referencesを指定する
Blog.includes(:posts).where('posts.name = "ABC"').references(:posts)
  • 関連先のカラムの条件指定にmergeを使う場合
Blog.includes(:posts).merge(Plan.where(name: 'ABC')).references(:posts)