Was ist der Unterschied zwischen select_related und prefetch_related in Django ORM?

291

In Django doc,

select_related() "folgt" Fremdschlüsselbeziehungen und wählt zusätzliche verwandte Objektdaten aus, wenn die Abfrage ausgeführt wird.

prefetch_related() führt für jede Beziehung eine separate Suche durch und führt das "Beitreten" in Python durch.

Was bedeutet es, "in Python mitzumachen"? Kann jemand mit einem Beispiel illustrieren?

Mein Verständnis ist, dass für Fremdschlüsselbeziehung verwenden select_related; und für die M2M-Beziehung verwenden prefetch_related. Ist das richtig?

NeoWang
quelle
2
Das Durchführen des Joins in Python bedeutet, dass der Join nicht in der Datenbank stattfindet. Mit einem select_related findet Ihr Join in der Datenbank statt und Sie haben nur eine Datenbankabfrage. Mit prefetch_related führen Sie zwei Abfragen aus, und dann werden die Ergebnisse vom ORM 'zusammengefügt', sodass Sie weiterhin object.related_set
Mark Galloway
3
Als Fußnote kann Timmy O'Mahony ihre Unterschiede auch anhand von Datenbanktreffern erklären: link
Mærcos
Dies kann Ihnen helfen, learnbatta.com/blog/working-with-select_related-in-django-89
anjaneyulubatta505

Antworten:

424

Ihr Verständnis ist größtenteils richtig. Sie verwenden, select_relatedwenn das Objekt, das Sie auswählen möchten, ein einzelnes Objekt ist, OneToOneFieldoder so ForeignKey. Sie verwenden, prefetch_relatedwenn Sie eine "Menge" von Dingen erhalten möchten, also ManyToManyFields wie Sie angegeben haben oder ForeignKeys umkehren . Um zu verdeutlichen, was ich mit "reverse ForeignKeys" meine, hier ein Beispiel:

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

Der Unterschied besteht darin, dass select_relatedein SQL-Join ausgeführt wird und die Ergebnisse daher als Teil der Tabelle vom SQL-Server zurückgegeben werden. prefetch_relatedführt andererseits eine andere Abfrage aus und reduziert daher die redundanten Spalten im ursprünglichen Objekt ( ModelAim obigen Beispiel). Sie können prefetch_relatedfür alles verwenden, für das Sie verwenden können select_related.

Die Kompromisse sind, dass prefetch_relatedeine Liste von IDs erstellt und gesendet werden muss, um sie an den Server zurückzusenden. Dies kann eine Weile dauern. Ich bin mir nicht sicher, ob es eine gute Möglichkeit gibt, dies in einer Transaktion zu tun, aber ich verstehe, dass Django immer nur eine Liste sendet und SELECT sagt ... WHERE pk IN (..., ..., ...) Grundsätzlich. In diesem Fall kann dies sehr gut sein, wenn die vorab abgerufenen Daten spärlich sind (sagen wir US-Staatsobjekte, die mit den Adressen von Personen verknüpft sind). Wenn sie jedoch näher an Eins-zu-Eins liegen, kann dies viel Kommunikation verschwenden. Versuchen Sie im Zweifelsfall beide und sehen Sie, welche Leistung besser ist.

Alles, was oben besprochen wurde, dreht sich im Wesentlichen um die Kommunikation mit der Datenbank. Auf der Python-Seite hat dies jedoch prefetch_relatedden zusätzlichen Vorteil, dass ein einzelnes Objekt verwendet wird, um jedes Objekt in der Datenbank darzustellen. Mit select_relateddoppelten Objekten werden in Python für jedes "übergeordnete" Objekt erstellt. Da Objekte in Python einen anständigen Speicheraufwand haben, kann dies ebenfalls eine Überlegung sein.

CrazyCasta
quelle
3
Was ist schneller?
Elad Silber
24
select_relatedist eine Abfrage, während prefetch_relatedzwei ist, so dass die erstere schneller ist. Aber select_relatedich werde dir nicht helfen für ManyToManyField's
bhinesley
31
@eladsilver Entschuldigung für die langsame Antwort. Es kommt tatsächlich darauf an. select_relatedVerwendet einen JOIN in SQL, während prefetch_relateddie Abfrage im ersten Modell ausgeführt wird, alle zum Vorabrufen erforderlichen IDs erfasst und anschließend eine Abfrage mit einer IN-Klausel in WHERE mit allen erforderlichen IDs ausgeführt wird. Wenn Sie sagen, 3-5 Modelle mit dem gleichen Fremdschlüssel, select_relatedwird mit ziemlicher Sicherheit besser sein. Wenn Sie Hunderte oder Tausende von Modellen haben, die denselben Fremdschlüssel verwenden, prefetch_relatedkönnte dies tatsächlich besser sein. Zwischendurch müssen Sie testen und sehen, was passiert.
CrazyCasta
1
Ich würde Ihren Kommentar zu Prefetch im Zusammenhang mit "macht im Allgemeinen nicht viel Sinn" bestreiten. Dies gilt für FK-Felder, die als eindeutig markiert sind, aber überall dort, wo mehrere Zeilen denselben FK-Wert haben (Autor, Benutzer, Kategorie, Stadt usw. usw.), verringert der Prefetch die Bandbreite zwischen Django und der Datenbank, ohne jedoch Zeilen zu duplizieren. Außerdem wird im Allgemeinen weniger Speicher in der Datenbank verwendet. Beides ist oft wichtiger als der Aufwand einer einzelnen zusätzlichen Abfrage. Angesichts dessen ist dies die beste Antwort auf eine einigermaßen beliebte Frage, die meiner Meinung nach in der Antwort vermerkt werden sollte.
Gordon Wrigley
1
@ GordonWrigley Ja, es ist schon eine Weile her, seit ich das geschrieben habe, also bin ich zurückgegangen und habe ein bisschen geklärt. Ich bin nicht sicher, ob ich mit dem Bit "verbraucht weniger Speicher in der Datenbank" einverstanden bin, aber ja zu allem. Und es kann sicher weniger Speicher auf der Python-Seite verbrauchen.
CrazyCasta
26

Beide Methoden erreichen den gleichen Zweck, auf unnötige Datenbankabfragen zu verzichten. Sie verwenden jedoch unterschiedliche Ansätze für die Effizienz.

Der einzige Grund für die Verwendung einer dieser Methoden besteht darin, dass eine einzelne große Abfrage vielen kleinen Abfragen vorzuziehen ist. Django verwendet die große Abfrage, um präventiv Modelle im Speicher zu erstellen, anstatt bei Bedarf Abfragen für die Datenbank durchzuführen.

select_relatedführt bei jeder Suche einen Join durch, erweitert jedoch die Auswahl um die Spalten aller verknüpften Tabellen. Dieser Ansatz hat jedoch eine Einschränkung.

Joins können die Anzahl der Zeilen in einer Abfrage multiplizieren. Wenn Sie einen Join über einen Fremdschlüssel oder ein Eins-zu-Eins-Feld ausführen, wird die Anzahl der Zeilen nicht erhöht. Viele-zu-viele-Joins haben diese Garantie jedoch nicht. Daher beschränkt sich Django select_relatedauf Beziehungen, die nicht unerwartet zu einer massiven Verbindung führen.

Das "Join in Python" für prefetch_relatedist etwas alarmierender als es sein sollte. Es wird eine separate Abfrage für jede zu verbindende Tabelle erstellt. Es filtert jede dieser Tabellen mit einer WHERE IN-Klausel wie:

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

Anstatt einen einzelnen Join mit möglicherweise zu vielen Zeilen durchzuführen, wird jede Tabelle in eine separate Abfrage aufgeteilt.

cdosborn
quelle
1

Wie die Django-Dokumentation sagt:

prefetch_related ()

Gibt ein QuerySet zurück, das automatisch in einem Stapel verwandte Objekte für jede der angegebenen Suchvorgänge abruft.

Dies hat einen ähnlichen Zweck wie select_related, da beide die Flut von Datenbankabfragen stoppen sollen, die durch den Zugriff auf verwandte Objekte verursacht wird, aber die Strategie ist ganz anders.

select_related erstellt einen SQL-Join und fügt die Felder des zugehörigen Objekts in die SELECT-Anweisung ein. Aus diesem Grund ruft select_related die zugehörigen Objekte in derselben Datenbankabfrage ab. Um jedoch die viel größere Ergebnismenge zu vermeiden, die sich aus der Verbindung über eine Beziehung mit mehreren Werten ergeben würde, ist select_related auf einwertige Beziehungen beschränkt - Fremdschlüssel und Eins-zu-Eins.

prefetch_related hingegen führt für jede Beziehung eine separate Suche durch und führt das 'Joining' in Python durch. Auf diese Weise können zusätzlich zu den Fremdschlüssel- und Eins-zu-Eins-Beziehungen, die von select_related unterstützt werden, viele-zu-viele- und viele-zu-eins-Objekte vorab abgerufen werden, was mit select_related nicht möglich ist. Es unterstützt auch das Vorabrufen von GenericRelation und GenericForeignKey, muss jedoch auf eine homogene Ergebnismenge beschränkt sein. Das Vorabrufen von Objekten, auf die von einem GenericForeignKey verwiesen wird, wird beispielsweise nur unterstützt, wenn die Abfrage auf einen ContentType beschränkt ist.

Weitere Informationen hierzu: https://docs.djangoproject.com/de/2.2/ref/models/querysets/#prefetch-related

Amin.B
quelle
1

Die bereits veröffentlichten Antworten sind durchgegangen. Ich dachte nur, es wäre besser, wenn ich eine Antwort mit einem tatsächlichen Beispiel hinzufügen würde.

Angenommen, Sie haben 3 verwandte Django-Modelle.

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)

Hier können Sie das M2Modell und seine relativen M1Objekte mithilfe von select_relationFeld und M3Objekte mithilfe von prefetch_relationFeld abfragen .

Wie bereits erwähnt M1, gibt die Beziehung von M2a ForeignKeynur 1 Datensatz für ein M2Objekt zurück. Gleiches gilt OneToOneFieldauch.

Aber M3die Beziehung von M2ist eine, ManyToManyFielddie eine beliebige Anzahl von M1Objekten zurückgeben kann.

Betrachten wir einen Fall , in dem Sie 2 haben M2Objekte m21, m22die gleiche haben 5 zugehörigen M3Objekte mit IDs 1,2,3,4,5. Wenn Sie zugeordnete M3Objekte für jedes dieser M2Objekte abrufen und select related verwenden, funktioniert dies folgendermaßen.

Schritte:

  1. m21Objekt suchen .
  2. Fragen Sie alle M3Objekte ab, die sich auf ein m21Objekt beziehen, dessen IDs sind 1,2,3,4,5.
  3. Wiederholen Sie dasselbe für das m22Objekt und alle anderen M2Objekte.

Wie wir gleiche haben 1,2,3,4,5IDs für beide m21, m22Objekte, wenn wir Option select_related verwenden, ist es die DB zweimal für den gleichen IDs abzufragen geht , die bereits abgerufen wurden.

Wenn Sie stattdessen prefetch_related verwenden und versuchen, M2Objekte abzurufen, werden alle IDs notiert, die Ihre Objekte zurückgegeben haben (Hinweis: nur die IDs), während Sie die M2Tabelle abfragen. Als letzten Schritt wird Django eine Abfrage an die M3Tabelle durchführen mit dem Satz aller IDs, die Ihre M2Objekte zurückgegeben haben. und verbinden Sie sie mit M2Objekten mit Python anstelle der Datenbank.

Auf diese Weise fragen Sie alle M3Objekte nur einmal ab, was die Leistung verbessert.

Jarvis
quelle