この記事はDjango prefetch_related()を使ってDBアクセスを高速化する方法 前編の続きです。前編ではprefetch_related()の基本的な使い方を解説しました。
後編では、prefetch_related()とPrefetchオブジェクトを組み合わせて使う方法を解説します。
サンプルコードのRestaurantクラスやPizzaクラスなどは前回の記事を参照してください。
Prefetchオブジェクト
Prefetchオブジェクトを使うと、prefetch_related()による事前呼び出しをより細かく操作できます。
Prefetchの一番シンプルな使い方は、文字列ベースのルックアップです。Prefetchオブジェクトを使わなくても同等に書けます。
from django.db.models import Prefetch
Restaurant.objects.prefetch_related(Prefetch('pizzas__toppings'))
# Prefetchオブジェクトを使った時と同等の書き方
Restaurant.objects.prefetch_related('pizzas__toppings')
オプションの queryset引数で独自のクエリセットを指定できます。この引数を使う事で、クエリセット並び順を変更できます。
Restaurant.objects.prefetch_related(
Prefetch('pizzas__toppings', queryset=Toppings.objects.order_by('name'))
)
さらにクエリ数を減らすために、可能なときは select_related() を使いましょう。
Pizza.objects.prefetch_related(
Prefetch(
'restaurants',
queryset=Restaurant.objects.select_related('best_pizza'),
)
)
オプションの to_attr引数 で、事前読み込みの結果を独自の属性に割り当てることもできます。結果はリストに直接格納されます。
これにより、異なる QuerySet で同じリレーションを複数回事前読み込みできるようになります。
vegetarian_pizzas = Pizza.objects.filter(vegetarian=True)
Restaurant.objects.prefetch_related(
Prefetch('pizzas', to_attr='menu'),
Prefetch('pizzas', queryset=vegetarian_pizzas, to_attr='vegetarian_menu'),
)
独自の to_attr で生成したルックアップは他のルックアップでも使用できます。
vegetarian_pizzas = Pizza.objects.filter(vegetarian=True)
# Prefetchクラスのto_attrで設定した'vegetarian_menu'を使って、
# toppingsの事前読み込みも行っています。
Restaurant.objects.prefetch_related(
Prefetch('pizzas', queryset=vegetarian_pizzas, to_attr='vegetarian_menu'),
'vegetarian_menu__toppings',
)
to_attr引数は、事前読み込みした結果をフィルタする際に推奨されます。これによりコードの可読性が上がります。
queryset = Pizza.objects.filter(vegetarian=True)
# 推奨 各レストランのvegetarian_pizzasを取得していることが明確です。
restaurants = Restaurant.objects.prefetch_related(
Prefetch('pizzas', queryset=queryset, to_attr='vegetarian_pizzas'))
vegetarian_pizzas = restaurants[0].vegetarian_pizzas
# 非推奨 all()を使っていますが、各レストランの全てのピザではありません。読み違える恐れがあります。よって、上記の方法より可読性が下がります。
restaurants = Restaurant.objects.prefetch_related(
Prefetch('pizzas', queryset=queryset))
vegetarian_pizzas = restaurants[0].pizzas.all()
独自の事前読み込みは、ForeignKey や OneToOneField のような単一のリレーションでも機能します。通常は select_related() を使うかも知れませんが、以下の様にQuerySet が役立つ場面も多くあります。
- 関連モデルに対してさらに事前読み込みを行う QuerySet を使用したい
- 関連オブジェクトのサブセットのみを事前読み込みしたい
- deferred fieldsの様なパフォーマンスを最適化する方法を使いたい
まとめ
prefetch_related()の機能を前編・後編に分けて解説しました。
後編はPrefetchオブジェクトを使うことで、さらに細かい事前読み込みが可能になります。DBアクセスの最適化もできます。
機能が豊富ですぐに理解できないかも知れませんが、DBアクセスの最適化に関わる機能なのでselect_related()と合わせて是非使いこなして欲しいです。
コメントを残す