Active Record の :include オプション
またまたN+1問題ネタです。
前回の記事で、N+1問題を回避するのにJOIN SQLを使うと、返されるオブジェクトの構造がイケていないということを書きました。
自分で、オブジェクトモデルをつくってみたわけですが、ちゃんとしたやり方がありました。
多くのO-Rマッピングフレームワークでは関連の取得はLAZYに行われますが、N+1問題の状況にあっては、これをEAGERに取得することでSQL発行回数を抑えることができます。返されるオブジェクトもちゃんとしたモデルの構造をしています。
:include オプションを使うと、貪欲に関連先テーブルのデータを取得しに行きます。
Book.all(:include => [:reviews]).each { |book| book.reviews.each { |review| puts review.body } }
このコードで発行されるSQLです。
Book Load (2.0ms) SELECT "books".* FROM "books" Review Load (0.6ms) SELECT "reviews".* FROM "reviews" WHERE ("reviews".book_id IN (1,2,3,4,5,6,7,8,9,10,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51))
BOOKSテーブルを全件検索した後、関連するREVIEWSテーブルのレコードを貪欲に取得しています。
ただし、取得するデータがあまりに大きい場合は、EAGERな取得はメモリを圧迫します。
メモリと性能のトレードオフということになります。
これは状況に応じて見極めていくしかないですね。