おもしろwebサービス開発日記

Ruby や Rails を中心に、web技術について書いています

eager loadingって何?

memcachedで快速アプリケーション − @IT

上記の記事で、こんなことが書いてありました。

以下に、Railを使用する際に用いられる一般的なパフォーマンス向上方法を紹介しよう。

O/Rマッパーを介さずに直接SQL文を発行する
ActiveRecord::Base::find_by_sqlメソッド
部分テンプレートキャッシング
→flagment_cache
レコード走査時に、関連先オブジェクトを一括生成する
→eager loading

eager lodingというフレーズは聞いたことあるような気がしたけど、具体的に何かはよくわからなかったので調べました。

eager loadingとは

例えば下記のようなTaskモデルとProjectモデルがあったとして

class Task < ActiveRecord::Base
  belongs_to :project
end

class Project < ActiveRecord::Base
  has_many :tasks
end

コントローラ側でtaskをDBより取得して

@tasks = Task.find(:all)

ビューでこんな風に書くと

<% @tasks.each do |task| -%>
  <%= task.project.name %>
<% end -%>

tasksテーブルに入っている列の数だけクエリが生成することになり、パフォーマンス的に大変よろしくありません。先にprojectsテーブルからもデータを取得しておけば、クエリの発行回数を減らすことができます。この、「先にデータを取得しておく」ことを eager loading というらしいです。

Railsでのeager loading実装の変化

eager loadingの言葉の意味を調べる過程で、Railsのeager loadingの実装(ActiveRecord::Base.findの:includeを指定したときの挙動)が2.1を境に変更されたことがわかったのでついでにメモしておきます。*1

Rails2.1以前は、find内で:includeを指定するとLEFT OUTER JOINを使用しクエリを一回だけ実行する形式だったのが、2.1からはテーブルの数と同じ回数のクエリを発行するようになりました。

User.all(:include => :company)

とすると、Rails2.1以前はLEFT OUTER JOINをしています。

SELECT
  `users`.`id`      AS t0_r0
  `users`.`email   AS t0_r1
  . . .
FROM
  `users`
  LEFT OUTER JOIN `companies` ON `companies`.id = `users`.company_id

Rails2.1以降は関連するテーブルごとにクエリを発行しています。

SELECT * FROM `users`;
SELECT * FROM `companies` 
  WHERE `companies`.id IN ('1','2', . . . );

Rails2.1以降の:includeの例外

includeで指定した関連のテーブルを:conditions内の条件に使うと2.1以前のLEFT OUTER JOINを使うようになるようです。

このように、companiesテーブルを:conditions内の条件で使用すると

User.all(:include => :company, 
  :conditions => "`companies`.`name` LIKE '%google%'")

Rails2.1以降でもLEFT OUTER JOINになってしまいます。

SELECT
  `users`.`id`      AS t0_r0
  `users`.`email   AS t0_r1
  . . .
FROM
  `users`
  LEFT OUTER JOIN `companies` ON `companies`.id = `users`.company_id
WHERE
  `companies`.`name` LIKE '%google%'

*1:基本的にSimpltry » Blog Archive » Rails 2.1 Eager Loadingの内容を訳しただけです