LoginSignup
2
2

More than 1 year has passed since last update.

includesで発行されるSQL【Rails】

Last updated at Posted at 2021-09-16

rails 6.0.2
ruby 2.7.1

includesとは

N+1問題を起こさないように、関連テーブルのデータをキャッシュしてくれるメソッド。
しかし、関連テーブルのデータをキャッシュするといっても
一体どのようにして、データを取得してきているのだろうか。

実はincludesは、モデルの関連など見て以下のどちらかの最適とrailsが判断したメソッドを実行している。
(このrailsの判断が正しいとは限らない。*後述)

preload eager_load
関連ごとにSQLを発行してキャッシュ。  left_joinを用いて、キャッシュ。

preload、eager_loadがどの様なSQLを発行してるか見ていく。

以下の様なモデルがあるとする。

user.rb
class User < ApplicationRecord
belong_to :country  #国
has_many  :posts    #投稿

end

preload

preloadは、関連ごとにSQLを発行してキャッシュする。
いったいどういうことかというと、以下のようなrubyコードでは

User.all.preload(:posts,:country)

以下のようなsqlが発行される。 

--user.allのsql
select * from users;
--postsのキャッシュのsql in句は、user.id
select * from posts where user_id in (1,2,3,4,5,6.....);
--countryのキャッシュのsql in句は、user.county_id
select * from countries where id in (1,2,3,4,5,6.....);

eager_load

left_joinを用いて、キャッシュ。
いったいどういうことかというと、以下のようなrubyコードでは

User.all.eager_load(:posts,:country)

以下のようなsqlが発行される。 

--user.allのsql兼、postsのキャッシュ兼、countryのキャッシュ
select * from users u
left join posts p on p.user_id = u.id
left join countries c c.id = u.countries ;

以上の様にsqlが発行される訳だが、
railsのincludesがpreload,eager_loadの使い分けを完璧にできる訳ではない。

例えば、以下のように複数のテーブルをキャッシュしたい時、
countryだけ、preloadで他は全てeager_loadみたいなことは出来ない。
全部preload or 全部eager_load のどちらかである。 

User.all.includes(posts: [:comments],:country,:profies)

preloadでキャッシャしたらまずいところ
eager_loadでキャッシュしたらまずいところがそれぞれあり、
railsでも使い分けをしている訳だが、
このようなことが起きると、意図せぬところでパフォーマンスの低下が起きてしまう。

したがって、各メリットデメリットを理解しておく必要がある。

メリットデメリット

preload

メリット

1.メモリの圧迫を防げる。
 例えば、中間テーブルを持つN対Nの関係のテーブルをeaager_loadを使ってキャッシュした場合。
 その実行結果のレコード数は単純に取得したかったデータ量よりもはるかに多くなってしまい、メモリを圧迫してしまうこ とになる。

 

デメリット 

① IN句が大きくなりすぎる場合がある。
 例えば、例で用いたrubyコードのUser.allが1万件あった場合、in句が膨大な長さになり、
 以下の様なことが起きる。

 ・ ネットワークI/Oを圧迫する。
 ・ rdbmsによってin句に指定できる数には限界がある、Oracleは、1000個らしい。
参考 https://oreno-it.info/archives/816

② 関連先テーブルを使って絞り込みなどができない。
別のsqlでキャッシュをしてるので、関連テーブルのカラムをwhereで使うことはできない。
つまり以下の様なrubyコードはエラーになる。

irb(main)> User.preload(:country).where(countries: {name: '日本'})

ActiveRecord::StatementInvalid (PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "members")
LINE 1: SELECT "users".* FROM "users" WHERE "members"."name" = $1 LI...

eager_load

メリット

① 関連先テーブルを使って絞り込みなどが出来る。
以下が実行可能。

irb(main)> User.eager_load(:country).where(countries: {name: '日本'})

② 一対一,N対一の関連テーブルを取得するのに良い。
 ・一対一,N対一の関連の場合、結合の結果のレコードが増えるわけではないので、
  結合によるメモリの圧迫は少ない。
 ・sqlの発行回数が少ない。(通信回数、rdbmsの構文解析などの時間が減る。)

デメリット

① 関連がN対ー,N対Nの場合のパフォーマンス
left_joinなので無駄なデータを沢山とってきてしまい、パフォーマンスは下がる。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2