ActiveRecordのfindで、:includeと:joinsを使ったときの違いって?

頭の中では:includeと:joinsの違いが分かったつもりになっているが、今まで実際に、内部的な動作を詳しく調べたことはなかった。
というわけで、ちゃんと理解するために実験を行ってみた。

モデルはこうなっている。

class Hoge < ActiveRecord::Base
  has_many :fugas
end

ビューはこんな感じ。

%h1 Listing hoges

%table
  %tr
    %th Name
  - @hoges.each do |hoge|
    %tr
      %td= h hoge.fugas[0].name
  @hoges = Hoge.all(:joins => :fugas)

の場合と、

  @hoges = Hoge.all(:include => :fugas)

の場合で、どういった挙動の違いが出るのかを、ログで確認してみた。


development.log
:includeの場合

  e[4;36;1mSQL (1.0ms)e[0m   e[0;1mSET SQL_AUTO_IS_NULL=0e[0m


Processing HogesController#index (for 0:0:0:0:0:0:0:1 at 2009-05-28 14:46:51) [GET]
  e[4;35;1mHoge Load (7.0ms)e[0m   e[0mSELECT * FROM `hoges` e[0m
  e[4;36;1mFuga Load (1.0ms)e[0m   e[0;1mSELECT `fugas`.* FROM `fugas` WHERE (`fugas`.hoge_id IN (1,2)) e[0m
Rendering hoges/index
Completed in 541ms (View: 450, DB: 9) | 200 OK [http://localhost/hoges]

:joinsの場合

  e[4;36;1mSQL (0.0ms)e[0m   e[0;1mSET SQL_AUTO_IS_NULL=0e[0m


Processing HogesController#index (for 0:0:0:0:0:0:0:1 at 2009-05-28 14:44:41) [GET]
  e[4;35;1mHoge Load (9.0ms)e[0m   e[0mSELECT `hoges`.* FROM `hoges` INNER JOIN `fugas` ON fugas.hoge_id = hoges.id e[0m
Rendering hoges/index
  e[4;36;1mFuga Load (1.0ms)e[0m   e[0;1mSELECT * FROM `fugas` WHERE (`fugas`.hoge_id = 1) e[0m
  e[4;35;1mFuga Load (1.0ms)e[0m   e[0mSELECT * FROM `fugas` WHERE (`fugas`.hoge_id = 2) e[0m
Completed in 602ms (View: 539, DB: 11) | 200 OK [http://localhost/hoges]


まとめ
:joinsの場合は、hoge.fugas[0].nameを呼び出すたびに、SQLが発行されている。
一方、:includeは、最初に関連モデルを全て取得しているため、hoge.fugas[0].nameは、キャッシュからロードされる。
当然、上記のような使い方をする場合は、:includeの方がはるかにパフォーマンスが高い。
:joinsは、関連モデルを検索条件に追加する場合に使うべし。