Railsの「SQLが実行される境界」

Railsの「SQLが実行される境界」

公開: 2026年01月02日

Tips
要約
  • RailsでのActiveRecord::Relationの理解を深め、SQLがいつ実行されるのかを学んだ。
  • scopeを分けてもパフォーマンスに影響しないことが分かり、条件別の読みやすいコードが推奨される。
  • SQLの実行タイミングを把握することは、より効率的なAPI実装に役立つことを認識した。
音声で記事を再生

はじめに

最近バックエンドの方がタスク結構多くて、「実行計画を意識したクエリ構築などできると良い」と言われました。(なんやそれ)
そもそもまだ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 は「DBに聞いていない」

クエリを組み立てるだけです!!

レビューで教えてもらって、一番腹落ちしたのがこの考え方です。

ActiveRecord::Relation を返している間は、
SQLはまだ実行されていない

whereorderlimit などは
SQLを実行しているのではなく、クエリを組み立てているだけ

つまり、さっきのコードも実際には、

  • scopeを積む

  • 条件を足す

  • ページネーションを指定する

「こういうSQLを投げる予定です」という設計図を作っている状態だった、ということです。

じゃあいつ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で何が変わるか

この仕組みを理解できてわかったこと検索系APIの書き方に対する見方が変わります。

  • 行を分けてもパフォーマンスは変わらない

  • scopeを細かく分けても問題ない

  • むしろ条件ごとに分けた方が読みやすい

変に「件数多いとパフォーマンス悪くなるんかな」って悩む必要もなくなります。

Railsの「境界」を知るということ

Railsはとても優秀なフレームワークで、SQLを意識しなくても仕事ができてしまいます。

でもそれは、SQLを考えなくていいと言うことではなくSQLを考える境界を隠してくれているというだけだったんだなぁと思いました。

ただこれって諸刃の剣というか、わからなくても動くもの作れちゃうって静かにバグらせたりしかねないことだなって思いました。

現にRailsは簡単って声も聞くんですが、どう考えてもクラス設計含めてRailsを正しく使う難易度は高いのではって私は感じます。。
そもそもバックエンドは業務ロジック部分なので簡単なわけないって感じることが多いです😇

scopeでレコードを返すメソッドを使ってしまうケース

悪い例ですが、Qiitaの記事で「scopeがnilを返すといけない」ってテーマで見かけて、即CTOにそうなん??って確認しましたw
結果「これは例が悪い!」とのことで理解が深まりました。

Modelにこんなscopeを書いてる記事でした。

scope :latest, -> { order(created_at: :desc).first }

一見すると問題なさそうに見えますよね😭
order まではRelationですが、first を呼んだ瞬間にSQLが実行されます。

firstActiveRecord::Relation を返さず、レコードそのもの(もしくは nil)を返すメソッドだからです。

つまり、このscopeを呼んだ瞬間に、まだ条件を積みたいかもしれないのに、ページネーションや追加条件を付ける前にRailsとSQLの境界を越えてしまっているということになります。

なのでscopeはActiveRecord::Relation を返すように書く必要があります!

レコードそのものを返すようにscopeを定義するとRelationを返す設計を崩してしまい、境界を越えるためにクエリが壊れるので気をつけようと思いました。

おわりに

SQLの書き方を覚える前にまず必要だったのは、

  • どこでSQLが実行されるのか

  • どこまではRailsが面倒を見てくれるのか

という境界の理解でした。
この先、実行計画を見るようになっていくみたいですが、それも結局は「SQLがいつ実行されているか」を理解した延長線にあるんだろうと思います。
そうじゃないとどんなSQLを実行しているかの理解を無視しているってことになるし、それで仕事ができるわけないですね、、!
その現実を知ったら正月がなんだ、勉強だ!ってなります😉(子供達がいるから無理な時が多いけどw)

まずはこの境界を知れただけでもRailsのコードを書く安心感はかなり変わりました。

次は私がRelationで組み立てた結果のSQLがどうなるかとパフォーマンスにどう影響が出るか勉強します!!!

シェア!

XThreads
ShiftB Logo
user
吉本茜
山口在住/二児の母/エンジニア
Loading...
記事一覧に戻る
XThreads
0