Ich habe eine Django-Ansicht, die ich optimieren möchte. Es zeigt eine Liste der übergeordneten Objekte auf einer Seite zusammen mit ihren untergeordneten Objekten. Das untergeordnete Modell hat den Fremdschlüssel zurück zum übergeordneten Modell, sodass select_related nicht zuzutreffen scheint.

class Parent(models.Model):
    name = models.CharField(max_length=31)

class Child(models.Model):
    name = models.CharField(max_length=31)
    parent = models.ForeignKey(Parent)

Eine naive Implementierung verwendet n + 1 Abfragen, wobei n die Anzahl der übergeordneten Objekte ist, d. H. Eine Abfrage zum Abrufen der übergeordneten Liste und eine Abfrage zum Abrufen der untergeordneten Elemente jedes übergeordneten Elements.

Ich habe eine Ansicht geschrieben, die die Aufgabe in zwei Abfragen erledigt - eine zum Abrufen der übergeordneten Objekte, eine zum Abrufen der zugehörigen untergeordneten Objekte und dann Python (das mir viel zu peinlich ist, um es hier zu posten), um alles wieder zusammenzusetzen .

Als ich das collections Modul der Standardbibliothek importierte, stellte ich fest, dass ich es wahrscheinlich falsch gemacht hatte. Es gibt wahrscheinlich einen viel einfacheren Weg, aber mir fehlt die Django-Erfahrung, um ihn zu finden. Alle Hinweise wäre sehr dankbar!

5
leon_matthews 10 Okt. 2012 im 11:23

3 Antworten

Beste Antwort

Fügen Sie ein related_name zum Fremdschlüssel und verwenden Sie dann {{X1 }} Methode, die zu Django 1.4 hinzugefügt wurde:

Gibt ein QuerySet zurück, das automatisch in einem einzigen abgerufen wird Batch, verwandte Objekte für jede der angegebenen Suchvorgänge.

Dies hat einen ähnlichen Zweck wie select_related, da beide sind Entwickelt, um die Flut von Datenbankabfragen zu stoppen, die durch verursacht wird Zugriff auf verwandte Objekte, aber die Strategie ist ganz anders:

  • select_related erstellt einen SQL-Join und schließt die Felder ein des zugehörigen Objekts in der Anweisung SELECT. Aus diesem Grund, select_related ruft die zugehörigen Objekte in derselben Datenbankabfrage ab. Um jedoch die viel größere Ergebnismenge zu vermeiden, die sich daraus ergeben würde select_related ist auf eine 'viele' Beziehung beschränkt und auf beschränkt einwertige Beziehungen - Fremdschlüssel und Eins-zu-Eins.

  • prefetch_related führt dagegen für jeden eine separate Suche durch Beziehung, und macht das "Beitreten" in Python. Dies ermöglicht es Vorabrufen von Many-to-Many- und Many-to-One-Objekten , was nicht möglich ist mit select_related zusätzlich zum Fremdschlüssel und eins zu eins Beziehungen, die von select_related unterstützt werden. Es unterstützt auch Prefetching von GenericRelation und GenericForeignKey.

class Parent(models.Model):
    name = models.CharField(max_length=31)

class Child(models.Model):
    name = models.CharField(max_length=31)
    parent = models.ForeignKey(Parent, related_name='children') 


>>> Parent.objects.all().prefetch_related('children')

Alle relevanten untergeordneten Elemente werden in einer einzigen Abfrage abgerufen und verwendet um QuerySets zu erstellen, die einen vorgefüllten Cache der relevanten haben Ergebnisse. Diese QuerySets werden dann im self.children.all() verwendet Anrufe.

Hinweis 1 Wie immer bei QuerySets werden alle nachfolgenden verketteten Methoden, die eine andere Datenbankabfrage implizieren, zuvor ignoriert Zwischengespeicherte Ergebnisse und Abrufen von Daten mithilfe einer neuen Datenbankabfrage.

Hinweis 2 Wenn Sie iterator() zum Ausführen der Abfrage verwenden, werden prefetch_related() Aufrufe seit diesen beiden ignoriert Optimierungen machen zusammen keinen Sinn.

3
defuz 10 Okt. 2012 im 08:25

Wenn Sie jemals mit mehr als 2 Ebenen gleichzeitig arbeiten müssen, können Sie einen anderen Ansatz zum Speichern von Bäumen in der Datenbank in Betracht ziehen, indem Sie MPTT

Kurz gesagt, es fügt Ihrem Modell Daten hinzu, die während der Aktualisierungen aktualisiert werden und einen wesentlich effizienteren Abruf ermöglichen.

3
Zeograd 10 Okt. 2012 im 10:44

Eigentlich ist select_related das, wonach Sie suchen. select_related erstellt einen JOIN, sodass alle benötigten Daten in einer Anweisung abgerufen werden. prefetch_related führt alle Abfragen gleichzeitig aus und speichert sie dann zwischen.

Der Trick hier besteht darin, nur das "Mitzumachen", was Sie unbedingt benötigen, um die Leistungseinbußen beim Beitritt zu verringern. "Was Sie unbedingt tun müssen" ist die lange Art zu sagen, die Sie vorbereiten sollten - Wählen Sie nur die Felder aus, die Sie später in Ihrer Ansicht oder Vorlage lesen werden. Hier finden Sie eine gute Dokumentation: https: //docs.djangoproject. com / de / 1.4 / ref / models / querysets / # select-related

Dies ist ein Ausschnitt aus einem meiner Modelle, bei dem ich auf ein ähnliches Problem gestoßen bin:

return QuantitativeResult.objects.select_related(
                'enrollment__subscription__configuration__analyte', 
                'enrollment__subscription__unit', 
                'enrollment__subscription__configuration__analyte__unit',
                'enrollment__subscription__lab',
                'enrollment__subscription__instrument_model'
                'enrollment__subscription__instrument',
                'enrollment__subscription__configuration__method',
                'enrollment__subscription__configuration__reagent',
                'enrollment__subscription__configuration__reagent__manufacturer',
                'enrollment__subscription__instrument_model__instrument__manufacturer'
                ).filter(<snip, snip - stuff edited out>)

In diesem pathologischen Fall ging ich von über 700 Anfragen auf nur eine zurück. Die Django-Debug-Symbolleiste ist Ihr Freund, wenn es um diese Art von Problem geht.

0
Ngure Nyaga 10 Okt. 2012 im 07:49