Djangoのドキュメントでは、
select_related()
は、外部キーの関係に「従い」、クエリの実行時に追加の関連オブジェクトデータを選択します。
prefetch_related()
はそれぞれの関係について別々の検索を行い、Pythonでは「結合」を行います。
「Pythonで参加する」とはどういう意味ですか?誰かが例で説明できますか?
私の理解するところでは、外部キーの関係にはselect_related
を使います。 M2Mの関係では、prefetch_related
を使用してください。これは正しいです?
あなたの理解はほとんど正しいです。選択しようとしているオブジェクトが単一のオブジェクトである場合はselect_related
を使用するので、OneToOneField
またはForeignKey
となります。一連のものを取得するときはprefetch_related
を使用します。そのため、ManyToManyField
sは前述のとおり、またはForeignKey
sを逆にします。 "reverse ForeignKey
s"の意味を明確にするために、例を示します。
class ModelA(models.Model):
pass
class ModelB(models.Model):
a = ForeignKey(ModelA)
ModelB.objects.select_related('a').all() # Forward ForeignKey relationship
ModelA.objects.prefetch_related('modelb_set').all() # Reverse ForeignKey relationship
違いは、select_related
はSQL結合を実行するため、結果がSQLサーバーからテーブルの一部として返されることです。一方、prefetch_related
は別のクエリを実行するため、元のオブジェクトの冗長列を減らします(上記の例ではModelA
)。 prefetch_related
を使用できるものなら何でもにselect_related
を使用できます。
トレードオフは、prefetch_related
がIDのリストを作成してサーバーに送信する必要があることです。これにはしばらく時間がかかります。トランザクション内でこれを行う良い方法があるかどうかはわかりませんが、Djangoは常にリストを送信してSELECT ... WHERE pk IN(...、...、...)と言っているだけです。基本的に。この場合、プリフェッチされたデータがまばらである場合(たとえば、米国の州オブジェクトが人の住所にリンクされているとします)、1対1に近いと通信が無駄になります。疑問がある場合は、両方を試して、どちらがより良いパフォーマンスを見せるかを確認してください。
上記で説明したことはすべて、基本的にデータベースとの通信に関するものです。しかしPython側ではprefetch_related
にはデータベース内の各オブジェクトを表すために単一のオブジェクトが使用されるという追加の利点があります。 select_related
を使用すると、Pythonでは "親"オブジェクトごとに重複するオブジェクトが作成されます。 Pythonのオブジェクトにはかなりの量のメモリオーバーヘッドがあるため、これも考慮する必要があります。
どちらの方法でも同じ目的が達成され、不要なdbクエリが不要になります。しかしそれらは効率のために異なったアプローチを使用します。
これらの方法のいずれかを使用する唯一の理由は、単一の大きなクエリが多くの小さなクエリよりも望ましい場合です。 Djangoはデータベースに対してオンデマンドでクエリを実行するのではなく、大規模なクエリを使用してメモリにモデルをプリエンプティブに作成します。
select_related
は各検索で結合を実行しますが、結合されたすべてのテーブルの列を含むようにselectを拡張します。ただし、この方法には注意が必要です。
結合は、クエリ内の行数を増やす可能性があります。外部キーまたは1対1フィールドで結合を実行しても、行数は増えません。ただし、多対多結合にはこの保証はありません。そのため、Djangoはselect_related
を予期せず大量の結合にならないような関係に制限します。
prefetch_related
の "pythonに参加する"はもう少し憂慮すべきです。結合するテーブルごとに個別のクエリを作成します。次のように、WHERE IN句を使用してこれらの各テーブルをフィルタリングします。
SELECT "credential"."id",
"credential"."uuid",
"credential"."identity_id"
FROM "credential"
WHERE "credential"."identity_id" IN
(84706, 48746, 871441, 84713, 76492, 84621, 51472);
潜在的に多すぎる行を使用して単一の結合を実行するのではなく、各テーブルは別々のクエリに分割されます。