DjangoにおけるN+1クエリ問題への対処
By khoanc, at: 2024年6月1日10:52
Estimated Reading Time: __READING_TIME__ minutes


熟練の開発者として、N+1クエリ問題のフラストレーションはよく理解しています。リストに対して1つのクエリを実行し、そのリスト内の各項目に対して追加のクエリを実行するという、パフォーマンスを低下させる厄介な問題です。Djangoでこの問題を検出して修正し、アプリケーションの動作をよりスムーズかつ高速にする方法を詳しく見ていきましょう。
N+1クエリの理解
各区に属する区のリストがあると想像してください。区とその区の情報を取り込む方法は次のようになります。
wards = Ward.objects.all()
for ward in wards:
name = ward.district.name
これは、区に対して1つのクエリ、各区の区名に対して1つのクエリという結果になり、N+1の問題につながります。
N+1クエリの検出
Django Debug Toolbar、Sentry、Scout APM などのツールは、これらの問題の特定に役立ちます。
たとえば、Django Debug Toolbarは、ページ上の過剰なクエリを強調表示します。私たちのGlintecoチームは、この記事で既に多くの便利なDjangoパッケージを指摘しています。
select_related()
を使用した解決方法
外部キーリレーションシップの場合は、select_related()
を使用してSQL結合を実行し、関連オブジェクトを1つのクエリで取得します。
wards = Ward.objects.select_related('district').all()
for ward in wards:
print(ward.district.name)
これにより、各区のdistrict
フィールドがプリロードされ、クエリの数が減少します。
prefetch_related()
を使用した解決方法
多対多リレーションシップと逆外部キーリレーションシップの場合、prefetch_related()
が役立ちます。これは、別のルックアップを実行し、Pythonで「結合」を実行します。
districts = District.objects.prefetch_related('wards').all()
for district in district:
wards = district.wards.all()
これにより、区の数に関係なく、2つのクエリですべての区のすべての区が取得されます。
実践例
著者とその書籍があるとしましょう。効率的に取得する方法は次のとおりです。
authors = Author.objects.prefetch_related('book_set').all()
for author in authors:
books = author.book_set.all()
結論
select_related()
とprefetch_related()
を活用することで、N+1クエリの問題を軽減し、Djangoアプリケーションのパフォーマンスを大幅に向上させることができます。 Django Debug Toolbar、Sentry、Scout APM などのツールは、これらの問題の検出に非常に役立ちます。