ActiveRecordはいつSQLを発行する?遅延評価をログで確認してみる
目次
はじめに
RailsでActiveRecordを使っていると、普段はSQLを直接書かなくてもデータベースを操作できます。
例えば、次のようなコードです。
users = User.where(active: true)
SQLにすると、次のような処理をイメージすると思います。
SELECT * FROM users WHERE active = true;
では、User.where(active: true) が実行された瞬間に、このSQLもデータベースへ送信されているのでしょうか。
実は、多くの場合そうではありません。
ActiveRecordには遅延評価という仕組みがあり、where などで検索条件を組み立てただけではSQLを実行せず、実際にデータが必要になったタイミングでSQLを発行します。
今回はRailsのログを見ながら、
「ActiveRecordはいつSQLを発行するのか?」
を確認していきます。
whereを書いただけではSQLは発行されない
まずは次のコードを考えます。
users = User.where(active: true)
where を見ると、すぐにSELECT文が実行されそうに見えます。
しかし、この時点で作られているのは基本的に
ActiveRecord::Relation
というオブジェクトです。
つまり、
active = true のUserを取得する
という検索条件を持った状態が作られているだけです。
まだ検索結果そのものを取得する必要がなければ、データベースへ問い合わせる必要はありません。
例えば、
users = User.where(active: true) users = users.order(created_at: :desc) users = users.limit(10)
と書いても、それぞれの行でSQLが実行されるわけではありません。
ActiveRecordは条件を組み合わせ、
SELECT "users".* FROM "users" WHERE "users"."active" = TRUE ORDER BY "users"."created_at" DESC LIMIT 10
のようなSQLを実行する準備をします。
where、order、limit などをつなげられるのも、途中で結果を取得するのではなく、ActiveRecord::Relation として条件を組み立てているためです。
eachした瞬間にSQLが発行される
では、いつSQLが実行されるのでしょうか。
例えば次のコードです。
users = User.where(active: true) users.each do |user| puts user.name end
where の時点では結果を使っていません。
しかし、
users.each
では、実際のUserデータが必要になります。
そのため、このタイミングでSQLが発行されます。
Railsのログを見ると、次のようなSQLを確認できます。
User Load (1.2ms) SELECT "users".* FROM "users" WHERE "users"."active" = TRUE
つまり処理の流れは、
User.where ↓ 検索条件を作る ↓ ActiveRecord::Relation ↓ まだSQLは実行しない ↓ each ↓ 実際のデータが必要になる ↓ SELECTを実行
というイメージです。
to_aでもSQLが発行される
to_a も分かりやすい例です。
users = User.where(active: true) users.to_a
to_a はRelationの検索結果を配列として扱うため、実際のレコードが必要になります。
そのため、
users.to_a
のタイミングでSELECTが実行されます。
ActiveRecordでは、
where order limit select joins
などでRelationを組み立て、
to_a each map
などで実際の結果を利用するときにSQLが発行される、と考えると理解しやすいです。
firstやfind_byはその場でSQLを発行する
すべてのActiveRecordメソッドがRelationを返すわけではありません。
例えば、
user = User.first
では、最初の1件という実際のレコードが必要です。
そのため、その場でSQLが発行されます。
SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
find_by も同様です。
user = User.find_by(email: "test@example.com")
これはRelationではなくUserオブジェクト、または nil を返すため、検索をその場で実行します。
大まかに整理すると、
where order limit joins ↓ Relationを返す ↓ SQLはまだ実行しない first find find_by take ↓ 実際のレコードを返す ↓ SQLを実行する
という違いがあります。
pluckやexists?もSQLを発行する
少し注意したいのが、レコードそのものを取得しないメソッドです。
例えば、
User.where(active: true).pluck(:id)
では、
SELECT "users"."id" FROM "users" WHERE "users"."active" = TRUE
のようなSQLがその場で実行されます。
pluck はRelationをさらに組み立てるメソッドではなく、必要な値を取得して配列として返すためです。
同じように、
User.where(active: true).exists?
では、条件に一致するレコードが存在するか確認するためのSQLが発行されます。
User.where(active: true).count
も件数を取得する必要があるため、通常は COUNT のSQLが実行されます。
つまり、
「SELECT * を実行するかどうか」ではなく、「その時点でデータベースの結果が必要かどうか」
で考えると分かりやすくなります。
sizeとlengthでも挙動が違う
ActiveRecordでは、似ているメソッドでもSQLの発行方法が違うことがあります。
例えば、
users = User.where(active: true) users.size
Relationがまだロードされていなければ、size は件数を取得するSQLを利用します。
SELECT COUNT(*) FROM "users" WHERE "users"."active" = TRUE
一方、
users.length
ではレコード自体をロードして、その配列の長さを取得します。
大量データを扱う場合、この違いによって処理時間やメモリ使用量が大きく変わることがあります。
単純にRubyコードだけを見るのではなく、
最終的にどのSQLが発行されているかを見ることが重要です。
Railsコンソールで確認するときは注意
遅延評価をRailsコンソールで確認するときには注意点があります。
例えば、
User.where(active: true)
と入力すると、SQLがすぐ実行されたように見える場合があります。
これはRailsコンソールが結果を画面に表示するためにRelationを inspect し、その結果としてレコードがロードされることがあるためです。
遅延評価を確認したい場合は、例えば次のようにします。
users = User.where(active: true); nil
これならRelationの中身を画面へ表示しません。
続いて、
users.loaded?
を確認します。
false であれば、まだレコードはロードされていません。
その後、
users.to_a
を実行すると、
User Load SELECT "users".* FROM "users" WHERE "users"."active" = TRUE
とログが出て、
users.loaded?
も true になります。
遅延評価を確認するには、この方法が分かりやすいです。
to_sqlを使うと実行前のSQLを確認できる
SQLを実行せず、どのようなSQLになるのかだけ確認したい場合は to_sql が便利です。
users = User .where(active: true) .order(created_at: :desc) .limit(10) puts users.to_sql
例えば次のようなSQLを確認できます。
SELECT "users".* FROM "users" WHERE "users"."active" = TRUE ORDER BY "users"."created_at" DESC LIMIT 10
to_sql はSQL文字列を確認するためのメソッドなので、このSELECT文自体をデータベースへ実行するわけではありません。
クエリを調査するときは、
to_sql ↓ どんなSQLになるか確認 development.log ↓ 実際にいつSQLが実行されたか確認
と使い分けると便利です。
まとめ
ActiveRecordでは、コードを書いた瞬間にすべてのSQLが実行されるわけではありません。
User.where(active: true)
のようなコードでは、まず ActiveRecord::Relation として検索条件が組み立てられます。
そして、
each to_a map first find_by pluck exists? count
など、実際にデータベースの結果が必要になったタイミングでSQLが発行されます。
この遅延評価を理解しておくと、
なぜここでSQLが発行されたのか
なぜ同じようなコードなのにSQLが違うのか
どの処理がデータベースアクセスを発生させているのか
を判断しやすくなります。
Railsのコードだけを見ていると、データベースアクセスが隠れて見えることがあります。
パフォーマンスを調べるときは、ActiveRecordのコードだけではなく、development.logに出ている実際のSQLまで確認することが重要です。



















