この記事を読むと得られること
- ✅ N+1とは何か をしっかり理解できる
- ✅ N+1の対策 を自信を持って打てるようになる
- ✅ データ取得時に意識すべきパフォーマンス観点 を身につけられる
データベースとアプリケーションの間でやりとりされるSQLクエリ。
その中でも開発初期によくある落とし穴が「N+1問題」です。
この問題を放置すると、リリース後にアプリが遅くなるなど重大な影響を及ぼします。
この記事では、N+1がどういう場面で発生しやすいのか、そしてどのように対策を打つべきかを丁寧に解説していきます。
N+1問題とは何か?どんな時に発生するのか?
N+1問題とは、データ取得の際に 不要に多くのSQLが実行されてしまう状態 を指します。
たとえば、以下のような処理を見てみましょう。
// ユーザー一覧を取得 users := db.Find(&[]User{}) for _, user := range users { // 各ユーザーのTodoを取得 db.Where("user_id = ?", user.ID).Find(&[]Todo{}) }
この場合、ユーザー一覧を取得する1回のクエリに加え、ユーザーごとに関連するTodoを取得しており、ユーザーの数だけクエリが追加で実行されます。
つまり、N人のユーザーに対してN+1回のクエリが発行されることになります。
データ取得で気をつけるべき2つのポイント
N+1を理解するためには、まずパフォーマンスに影響する基本的な観点を押さえることが大切です。
1. SQLのリクエスト回数
クエリ数が増えるほど、アプリケーションとデータベースの間の通信が増え、処理速度に悪影響を及ぼします。
2. テーブル結合(JOIN)のコスト
複数のテーブルを結合するとDB内での処理が重くなります。大量データをJOINする場合は特に注意が必要です。
N+1の何が問題なのか?
要点は「SQLのリクエスト回数が増えてしまう」という点に尽きます。
1件ずつ関連データを取得することで、ユーザー数に比例してクエリ数が増加。
その結果、処理が遅くなったり、DBへの負荷が急増したりと、アプリケーションのパフォーマンスが著しく悪化します。
N+1問題の対策方法
N+1問題を回避するためには、以下のような方法があります。
● Preload
(GORMなどのORマッパーで使用)
関連データをあらかじめ一括で取得しておく方法です。
db.Preload("Todos").Find(&users)
これにより、ユーザー一覧と各ユーザーのTodoを 2クエリで一気に取得 できます。
● JOIN
を使う(用途に応じて)
場合によっては JOIN
を使って関連データをまとめて取得する方法も有効です。ただし、JOINを多用するとデータ量が膨らみ、パフォーマンスに影響する可能性もあるため、慎重に使う必要があります。
💡 PreloadとJOIN、どちらを選ぶべきか?
この判断は場面ごとに異なり、データ構造や用途に応じた知識が必要になります。
このテーマは中級者以上向けの内容になるため、別記事で詳しく解説します。
まとめ:中級者への第一歩は「パフォーマンス意識」
今回は「N+1問題」について基本から対策までを紹介しました。
重要なのは、単にコードを書くのではなく、クエリの数や処理の重さに対する意識を常に持つことです。
- クエリが何回実行されているか?
- 1回のクエリでどれだけのデータを持ってくるか?
- PreloadやJOINは適切に使われているか?
こうした視点を身につければ、日々の開発で「なんとなく書いている処理」が一段レベルアップし、パフォーマンスを意識できる中級者へのステップアップになります。
📘 次回予告:PreloadとJOINの違いと選び方を徹底解説!
さらに深く知りたい方のために、別記事で実際の使い分けパターンやベンチマーク結果も紹介する予定です。
このままブログに貼ってもOKな形に整えましたが、必要なら図やコード例を増やすカスタマイズも対応できます。どうしますか?