【要約】N+1 問題を、発行される SQL を数えて理解する [Zenn_Python] | Summary by TechDistill
> Source: Zenn_Python
Execute Primary Source
// Problem
開発者がORMを用いて関連データを取得する際、意図せず大量のSQLを発行するN+1問題に直面する。これは、関連データのアクセスが「遅延ロード」によって、ループ内での属性アクセス時に個別に発生するためである。
- ・SQLの実行回数がデータ数 N に比例して増加する。
- ・クエリ単体は軽量でも、DBとのネットワーク往復(RTT)が累積し、致命的な遅延を招く。
- ・データ増に伴い性能が線形に悪化するため、開発環境では検知が困難である。
// Approach
著者は、N+1問題を「往復回数を定数に落とす」という観点から、以下の3つのアプローチで解決を図っている。
- ・
joinedload: JOINを用いて1回のクエリに集約する。多対一の取得に適している。 - ・
selectinload: 親の取得後にIN句を用いて関連をまとめて取得する。一対多の取得に適している。 - ・集計関数の利用: 関連レコード自体が不要な場合は、JOINとGROUP BYを用いて集計のみを行う。
// Result
実測により、N+1発生時(101本)の157.4msに対し、
joinedload(1本)は11.6ms、selectinload(2本)は12.6msと、劇的な改善を確認した。- ・
selectinloadは、複数のコレクションをJOINした際の行数増大(デカルト積)を防げる。 - ・テストでクエリ数を制限することで、再発を防止する具体的な手法を提示した。
Senior Engineer Insight
> N+1は単なる遅延ではなく、Lambdaの課金増やAPIのレート制限、タイムアウトといった「機能停止」を招く。単に「クエリ1本にすること」を目的とせず、往復回数を定数化することに注力すべきだ。テストでクエリ数をアサートする手法は、大規模開発における防波堤として極めて有効である。