DjangoにおけるN+1クエリ問題への対処

By khoanc, at: 2024年6月1日10:52

Estimated Reading Time: __READING_TIME__ minutes

Tackling the N+1 Queries Problem in Django
Tackling the N+1 Queries Problem in Django

熟練の開発者として、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 ToolbarSentryScout 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 ToolbarSentryScout APM などのツールは、これらの問題の検出に非常に役立ちます。

Tag list:
- Django
- Django Queryset
- Django Manager
- Django Query
- Django N+1 Queries
- Django Query Optimization
- Query Optimization

Related

Django Python

Read more
Python

Read more

Subscribe

Subscribe to our newsletter and never miss out lastest news.