FIG-013

N+1問題 — 職員室に10回も聞きに行かない

データベース 2026.06.12 公開 読了 約10分

「ブログの記事一覧に、記事10件とそれぞれの著者名を表示したい」— ごく普通の要件です。ところが素直に書いたコードが、データベースへの問い合わせを11回も発行していることがあります。これがN+1問題。アプリが遅くなる原因の、定番中の定番です。

たとえるなら、職員室(データベース)と教室(アプリ)の間を、生徒の名前を1人ずつ聞きに10回往復するか、名簿を1回でもらってくるかの違いです。下の図1で、2つのやり方を実際に走らせて比べてみてください。

ボタンを押すと問い合わせが走ります。まずは「やり方①」からどうぞ。
ROUND TRIP — アプリとデータベースの間の往復
APP
アプリ(教室)
DB(職員室)
クエリ 0
往復の待ち時間 0 ms
PAGE — できあがっていく記事一覧
朝のコーヒーの淹れ方
週末の散歩コース
パンを焼く日々
机の上の整理術
雨の日の読書記録
ベランダ菜園入門
夜更かしと睡眠の話
旅先の小さな写真展
自作キーボード沼
今月読んだ技術書
QUERY LOG — 発行されたSQL
まだクエリは発行されていません
図1 — 同じ一覧ページを作るのに、往復11回 vs 1回

1回1回は速い。積もると遅い

N+1問題のたちが悪いところは、1つ1つのクエリはまったく遅くないことです。図1のとおり、効いてくるのはクエリの中身ではなく往復そのもの。アプリとデータベースは別のコンピュータにいることが多く、1往復ごとにネットワークの待ち時間(数ミリ秒)がまるごとかかります。記事が10件なら11往復、100件なら101往復 — 表示する件数(N)に比例して、勝手に往復が増えていくのです。

原因のほとんどは「一覧をループで回しながら、ループの中で関連データを1件ずつ取りに行く」コードです。ORM(RailsのActive RecordやPrismaなど)を使っていると、post.author.nameと書いただけで裏でクエリが走るため、SQLを1行も書いていないのに発生するのが見つけにくさの理由です。

対策はシンプルで、「あとで1人ずつ聞く」のをやめて最初にまとめて聞くこと。図1の②のようにJOINで1回で取るか、記事を取ったあとに「この著者ID全員分の名前をください」とIN句で2回目にまとめて取る(ORMのeager loading、includesinclude)のが定石です。11回が1〜2回になります。

用語ミニ辞書
N+1問題
一覧N件の取得1回+関連データの取得N回で、計N+1回のクエリが走ってしまうこと。
JOIN
複数のテーブルをつなげて、1回のクエリでまとめて取る書き方。
eager loading
ORMに「関連データも最初にまとめて読んでおいて」と指示する機能。N+1の定番対策。

まとめ

N+1問題は「クエリが重い」のではなく「往復が多い」問題です。開発中はN=10で気づかず、本番でN=1000になって悲鳴を上げる — というのがお決まりのパターン。一覧ページが遅いと感じたら、まずクエリログを開いて同じ形のSQLがずらっと並んでいないかを見てください。それがN+1の足跡です。