熟練の開発者として、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()を使用してSQL結合を実行し、関連オブジェクトを1つのクエリで取得します。

wards = Ward.objects.select_related('district').all()
for ward in wards:
    print(ward.district.name)


これにより、各区のdistrictフィールドがプリロードされ、クエリの数が減少します。

多対多リレーションシップと逆外部キーリレーションシップの場合、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 などのツールは、これらの問題の検出に非常に役立ちます。