Railsの「SQLが実行される境界」
公開: 2026年01月02日
最近バックエンドの方がタスク結構多くて、「実行計画を意識したクエリ構築などできると良い」と言われました。(なんやそれ)
そもそもまだRailsが怪しいんだよなぁって思ってる中で、解せた話があるのでそれについて書きます。
パフォーマンスを意識してAPI実装するなら最低ライン感もあるし重要だなと思ったので、徹底的に調べて聞いて理解しました!!!
だいぶ前ですが検索・フィルター・ページネーションを含むAPIを実装したときの話です。
users = User.all
users = users.by_status(params[:status])
users = users.by_keyword(params[:keyword])
users = users.page(params[:page])正直このコードを書いたとき、こう思っていました。
「これ、行を分けるたびにSQL実行されてたらパフォーマンスめちゃくちゃ悪くない?」
scopeを積めば積むほど、DBに何回も問い合わせているイメージがあったからです。
でも聞いてみると意外なことを教えてもらいました。
「この時点ではまだSQLは実行されてないよ!」
どういうこと??だったんですが、このあたりから自分がRailsとSQLの境界を全然分かっていなかったということに薄々気づきました。
その時点では意味不明でしたがw
最近また別のAPIでまたその話になって。
わかってなかったことを理解して(無知の知って他人のレビューがないとできない)、もう少し別の知識が入ったことにより理解できた感じがあります。
当時の認識はこんな感じでした。
where を呼ぶ
→ SQLが実行される
scopeを呼ぶ
→ DBに問い合わせている
行を分ける
→ 処理回数が増える
なので、
条件を1行で書いた方が速い
scopeを分けると遅くなる
と、なんとなく思っていました。
でもこれは Railsの抽象に対する誤解 でした。
クエリを組み立てるだけです!!
レビューで教えてもらって、一番腹落ちしたのがこの考え方です。
ActiveRecord::Relation を返している間は、
SQLはまだ実行されていない
where や order、limit などは
SQLを実行しているのではなく、クエリを組み立てているだけ。
つまり、さっきのコードも実際には、
scopeを積む
条件を足す
ページネーションを指定する
「こういうSQLを投げる予定です」という設計図を作っている状態だった、ということです。
じゃあいつSQLは実行されるのか??って思いました。
それは、
each
to_a
first
find_by
count
など、実際のデータが必要になった瞬間です。
このとき初めて、それまで積み上げてきた条件をまとめて1回のSQLとしてDBに投げます。
レビューで特に分かりやすかった説明がこれです。
ActiveRecord::Relation を返すかどうかで、
SQLがもう実行されているかが一発で分かる
たとえば、以下のようなメソッドたちはRelationを返すだけで、SQLは実行されません。
where
order
select
limit
offset
group
having
joins / left_joins
includes / preload / eager_load
distinct
merge / or
逆に、
find_by
first
last
count
などのメソッドは、その場でSQLを実行します。
.class を見るRailsは全部の値がオブジェクトです!(これも教わった)
なのでそのメソッドが何を返すかはクラスを確認したらわかります。
どうしても混乱したら、こうやって確認すればいいと教えてもらいました。
User.where(status: 'active').classここで ActiveRecord::Relation が返ってきている間は、まだDBには問い合わせていないということがわかります。
この仕組みを理解できてわかったこと検索系APIの書き方に対する見方が変わります。
行を分けてもパフォーマンスは変わらない
scopeを細かく分けても問題ない
むしろ条件ごとに分けた方が読みやすい
変に「件数多いとパフォーマンス悪くなるんかな」って悩む必要もなくなります。
Railsはとても優秀なフレームワークで、SQLを意識しなくても仕事ができてしまいます。
でもそれは、SQLを考えなくていいと言うことではなくSQLを考える境界を隠してくれているというだけだったんだなぁと思いました。
ただこれって諸刃の剣というか、わからなくても動くもの作れちゃうって静かにバグらせたりしかねないことだなって思いました。
現にRailsは簡単って声も聞くんですが、どう考えてもクラス設計含めてRailsを正しく使う難易度は高いのではって私は感じます。。
そもそもバックエンドは業務ロジック部分なので簡単なわけないって感じることが多いです😇
悪い例ですが、Qiitaの記事で「scopeがnilを返すといけない」ってテーマで見かけて、即CTOにそうなん??って確認しましたw
結果「これは例が悪い!」とのことで理解が深まりました。
Modelにこんなscopeを書いてる記事でした。
scope :latest, -> { order(created_at: :desc).first }一見すると問題なさそうに見えますよね😭order まではRelationですが、first を呼んだ瞬間にSQLが実行されます。
first は ActiveRecord::Relation を返さず、レコードそのもの(もしくは nil)を返すメソッドだからです。
つまり、このscopeを呼んだ瞬間に、まだ条件を積みたいかもしれないのに、ページネーションや追加条件を付ける前にRailsとSQLの境界を越えてしまっているということになります。
なのでscopeはActiveRecord::Relation を返すように書く必要があります!
レコードそのものを返すようにscopeを定義するとRelationを返す設計を崩してしまい、境界を越えるためにクエリが壊れるので気をつけようと思いました。
SQLの書き方を覚える前にまず必要だったのは、
どこでSQLが実行されるのか
どこまではRailsが面倒を見てくれるのか
という境界の理解でした。
この先、実行計画を見るようになっていくみたいですが、それも結局は「SQLがいつ実行されているか」を理解した延長線にあるんだろうと思います。
そうじゃないとどんなSQLを実行しているかの理解を無視しているってことになるし、それで仕事ができるわけないですね、、!
その現実を知ったら正月がなんだ、勉強だ!ってなります😉(子供達がいるから無理な時が多いけどw)
まずはこの境界を知れただけでもRailsのコードを書く安心感はかなり変わりました。
次は私がRelationで組み立てた結果のSQLがどうなるかとパフォーマンスにどう影響が出るか勉強します!!!
