Django ORMのselect_relatedとprefetch_relatedの違いは何ですか?


291

Djangoドキュメントでは、

select_related() 外部キーの関係を「フォロー」し、クエリの実行時に追加の関連オブジェクトデータを選択します。

prefetch_related() 関係ごとに別々のルックアップを行い、Pythonで「結合」を行います。

「Pythonで参加する」とはどういう意味ですか?誰かが例で説明できますか?

私の理解では、外部キーの関係にはを使用しselect_relatedます。M2M関係の場合はを使用しますprefetch_related。これは正しいです?


2
Pythonで結合を実行すると、データベースで結合が発生しなくなります。select_relatedを使用すると、データベースで結合が行われ、データベースクエリが1回だけ発生します。prefetch_relatedを使用すると、2つのクエリを実行し、結果はORMによって「結合」されるため、object.related_setを入力できます
Mark Galloway

3
脚注として、ティミーO'Mahony氏は、データベースのヒットを使用して、その違いを説明することができます:リンクを
Mærcos

これは、learnbatta.com
blog

回答:


424

あなたの理解はほとんど正しいです。あなたは使うselect_relatedあなたが選択することになるだろうというオブジェクトが単一のオブジェクト、そうであるときOneToOneFieldForeignKeyprefetch_related物事の「セット」を取得するときに使用するので、ManyToManyFieldsはsと同じか、またはForeignKeysを逆にします。「リバースForeignKeys」の意味を明確にするために、ここに例を示します。

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のオブジェクトには、かなりのメモリオーバーヘッドがあるため、これも考慮する必要があります。


3
でも何が速いの?
eladシルバー2017

24
select_relatedは1つのクエリprefetch_relatedですが、2つは前者の方が高速です。しかし、select_relatedためにあなたを助けにはなりませんManyToManyFieldさん
bhinesley

31
@eladsilver返信が遅くなってすみません。それは実際に依存します。select_relatedSQLでJOINを使用するのに対しprefetch_related、最初のモデルでクエリを実行し、プリフェッチする必要があるすべてのIDを収集してから、必要なすべてのIDを使用してWHEREにIN句を含むクエリを実行します。同じ外部キーを使用する3〜5個のモデルがあるとしたら、select_relatedほぼ間違いなくより良いでしょう。同じ外部キーを使用する数百または数千のモデルがある場合、prefetch_related実際にはより優れている可能性があります。その間に、何が起こるかをテストして確認する必要があります。
CrazyCasta 2017

1
プリフェッチ関連の「一般的にはあまり意味がありません」についてのあなたのコメントに異議を唱えます。一意とマークされたFKフィールドについても同様ですが、複数の行が同じFK値(作成者、ユーザー、カテゴリ、都市など)を持っている場所では、プリフェッチによってDjangoとDB間の帯域幅が削減されますが、行の重複はありません。また、通常、DBのメモリ使用量も少なくなります。これらのいずれかは、1つの追加クエリのオーバーヘッドよりも重要であることがよくあります。これがかなり人気のある質問のトップアンサーであることを考えると、私はそれをアンサーに記載する必要があります。
ゴードンリグリー2017

1
@GordonWrigleyええ、それを書いてからしばらく経っていたので、戻って少しわかりやすくしました。「DBのメモリ使用量が少ない」ビットに同意するかどうかはわかりませんが、すべてに同意します。また、Python側で使用するメモリを減らすことができます。
CrazyCasta 2017

26

どちらの方法も、不要なdbクエリを無視するという同じ目的を達成します。しかし、彼らは効率のために異なるアプローチを使用しています。

これらの方法のいずれかを使用する唯一の理由は、単一の大きなクエリが多くの小さなクエリよりも望ましい場合です。Djangoは、データベースに対してオンデマンドクエリを実行するのではなく、大きなクエリを使用してメモリにモデルを事前に作成します。

select_related各ルックアップで結合を実行しますが、結合されたすべてのテーブルの列を含めるように選択を拡張します。ただし、このアプローチには注意が必要です。

結合は、クエリの行数を増やす可能性があります。外部キーまたは1対1のフィールドで結合を実行しても、行数は増えません。ただし、多対多の結合にはこの保証はありません。したがって、Djangoは制限しますselect_relatedは、予期せず大規模な結合を引き起こさない関係にしています。

「pythonで参加する」ためには、prefetch_relatedより多くのそれがあるべき驚かす少しです。結合するテーブルごとに個別のクエリを作成します。次のように、これらの各テーブルをWHERE IN句でフィルタリングします。

SELECT "credential"."id",
       "credential"."uuid",
       "credential"."identity_id"
FROM   "credential"
WHERE  "credential"."identity_id" IN
    (84706, 48746, 871441, 84713, 76492, 84621, 51472);

行が多すぎる可能性のある単一の結合を実行するのではなく、各テーブルは個別のクエリに分割されます。


1

Djangoのドキュメントによると:

prefetch_related()

指定された各ルックアップの関連オブジェクトを単一のバッチで自動的に取得するQuerySetを返します。

これは、select_relatedと同様の目的を持っています。どちらも、関連するオブジェクトへのアクセスによって引き起こされるデータベースクエリの大洪水を阻止するように設計されていますが、戦略はまったく異なります。

select_relatedは、SQL結合を作成し、SELECTステートメントに関連オブジェクトのフィールドを含めることで機能します。このため、select_relatedは、同じデータベースクエリ内の関連オブジェクトを取得します。ただし、「多数」の関係を介して結合することにより生じるはるかに大きな結果セットを回避するために、select_relatedは単一値の関係(外部キーと1対1)に制限されています。

一方、prefetch_relatedは、関係ごとに個別の検索を実行し、Pythonで「結合」を実行します。これにより、select_relatedでサポートされている外部キーと1対1の関係に加えて、select_relatedを使用して実行できない多対多および多対1のオブジェクトをプリフェッチできます。GenericRelationおよびGenericForeignKeyのプリフェッチもサポートしますが、同種の結果セットに制限する必要があります。たとえば、GenericForeignKeyによって参照されるオブジェクトのプリフェッチは、クエリが1つのContentTypeに制限されている場合にのみサポートされます。

これに関する詳細:https : //docs.djangoproject.com/en/2.2/ref/models/querysets/#prefetch-related


1

すでに投稿された回答を確認します。実際の例で答えを追加した方がいいと思いました。

関連する3つのDjangoモデルがあるとします。

class M1(models.Model):
    name = models.CharField(max_length=10)

class M2(models.Model):
    name = models.CharField(max_length=10)
    select_relation = models.ForeignKey(M1, on_delete=models.CASCADE)
    prefetch_relation = models.ManyToManyField(to='M3')

class M3(models.Model):
    name = models.CharField(max_length=10)

ここでは、問い合わせることができM2モデルとその相対的なM1使用してオブジェクトをselect_relationフィールドとM3使用してオブジェクトをprefetch_relationフィールド。

ただし、M1のリレーションはM2isであるForeignKeyことを説明したように、オブジェクトのレコードは1つだけ返されM2ます。同じことが当てはまりOneToOneFieldます。

しかし、M3の関係はでM2あり、ManyToManyField任意の数のM1オブジェクトを返す可能性があります。

2つのM2オブジェクトがありm21、ID m22が同じ5つのM3オブジェクトが関連付けられている場合を考えます1,2,3,4,5M3これらの各オブジェクトに関連付けられたオブジェクトをフェッチするときにM2、select relatedを使用すると、これが動作します。

手順:

  1. m21オブジェクトを検索します。
  2. IDがであるM3オブジェクトに関連するすべてのオブジェクトを照会しm21ます1,2,3,4,5
  3. m22オブジェクトと他のすべてのM2オブジェクトについて同じことを繰り返します。

私たちは同じ持っているように1,2,3,4,5、両方のIDをm21m22私たちはselect_relatedオプションを使用する場合は、オブジェクトは、すでにフェッチされた同じIDの二回DBを照会するために起こっています。

代わりに、prefetch_relatedを使用する場合、M2オブジェクトを取得しようとすると、M2テーブルのクエリ中にオブジェクトが返したすべてのID(注:IDのみ)が記録され、最後のステップとして、DjangoがM3テーブルにクエリを実行しますM2オブジェクトが返したすべてのIDのセット。M2データベースの代わりにPythonを使用してオブジェクトに結合します。

この方法では、すべてのM3オブジェクトを1回だけクエリするので、パフォーマンスが向上します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.