wake-up-neo.com

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

In Django doc,

select_related() "folgt" Fremdschlüsselbeziehungen und wählt zusätzliche verwandte Objektdaten aus, wenn es seine Abfrage ausführt.

prefetch_related() führt eine separate Suche für jede Beziehung durch und führt die "Verknüpfung" in Python durch.

Was bedeutet es, "das Zusammenfügen in Python zu machen"? Kann jemand mit einem Beispiel illustrieren?

Meines Wissens nach verwenden Sie für Fremdschlüsselbeziehungen select_related; und für M2M-Beziehung verwenden Sie prefetch_related. Ist das richtig?

225
NeoWang

Ihr Verständnis ist größtenteils richtig. Sie verwenden select_related Wenn das Objekt, das Sie auswählen möchten, ein einzelnes Objekt ist, ist OneToOneField oder ForeignKey. Sie verwenden prefetch_related Wenn Sie eine "Menge" von Dingen erhalten wollen, so 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 ist, dass select_related führt einen SQL-Join durch und ruft daher die Ergebnisse als Teil der Tabelle vom SQL-Server ab. prefetch_related führt andererseits eine andere Abfrage aus und reduziert daher die redundanten Spalten im ursprünglichen Objekt (ModelA im obigen Beispiel). Sie können prefetch_related für alles, was du benutzen kannst select_related zum.

Die Kompromisse sind, dass prefetch_related muss eine Liste von IDs erstellen und an den Server zurücksenden, dies kann eine Weile dauern. Ich bin 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 (... , ..., ...) In diesem Fall kann dies sehr gut sein, wenn die vorab abgerufenen Daten spärlich sind (z. B. US-Bundesstaatenobjekte, die mit den Adressen von Personen verknüpft sind) Verschwenden Sie viel Kommunikation. Wenn Sie Zweifel haben, probieren Sie beide aus und sehen Sie, welche Leistung besser ist.

Alles, was oben besprochen wurde, betrifft im Wesentlichen die Kommunikation mit der Datenbank. Auf der Python Seite jedoch prefetch_related hat den zusätzlichen Vorteil, dass ein einzelnes Objekt verwendet wird, um jedes Objekt in der Datenbank darzustellen. Mit select_related doppelte Objekte werden in Python für jedes "Eltern" -Objekt erstellt. Da Objekte in Python einen angemessenen Speicheraufwand haben, kann dies auch eine sein Berücksichtigung.

345
CrazyCasta

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

Der einzige Grund, eine dieser Methoden zu verwenden, ist, wenn 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_related führt bei jeder Suche einen Join durch, erweitert die Auswahl jedoch 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-Verknüpfungen verfügen jedoch nicht über diese Garantie. Also, Django schränkt select_related für Beziehungen, die nicht unerwartet zu einer massiven Verknüpfung führen.

Das "Join in Python" für prefetch_related ist etwas alarmierender als es sein sollte. Es wird eine separate Abfrage für jede zu verknüpfende 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.

15
cdosborn