HOWTO · Django
Select_related-Methode in Django
Der Artikel erklärt, was ein QuerySet ist, wie Django Abfragen verarbeitet und wie `select_related()` ein verwandtes Objekt mit der Abfrage lädt, ohne Zeilen zu filtern.
Auf dieser Seite
Verwenden Sie select_related(), wenn ein Django-QuerySet ein einzelnes verwandtes Objekt lesen soll, etwa die Abteilung eines Mitarbeiters. Das Objekt wird mit der Abfrage geladen, sodass wiederholte Zugriffe separate Abrufe des verwandten Objekts vermeiden können; Zeilen werden dadurch nicht gefiltert.
Was select_related() bewirkt
select_related() ist eine QuerySet-Methode, mit der Django verwandte Objekte in derselben Datenbankabfrage wie die Objekte des QuerySets abruft. Sie ist nützlich, wenn der Code eine Beziehung mit genau einem Objekt lesen soll, etwa die Abteilung eines Mitarbeiters. Django verbindet die verwandte Tabelle und lädt das zugehörige Objekt, sodass dessen Abruf nicht für jeden Mitarbeiter eine separate Abfrage erfordert.
Die Methode ändert, wie verwandte Daten geladen werden; sie filtert weder Mitarbeiter noch Abteilungen. Verwenden Sie gewöhnliche QuerySet-Filter wie filter(), um die ausgewählten Zeilen zu bestimmen. Der Name select_related() beschreibt, dass beim Auswählen von Objekten einer Modellbeziehung gefolgt wird, nicht dass passende verwandte Zeilen eingeschränkt werden.
Das Abfrageproblem mit verwandten Objekten verstehen
Angenommen, ein Django-Projekt enthält die Modelle Department und Employee, und Employee.department ist ein ForeignKey auf Department. Ein QuerySet wie Employee.objects.all() ruft zunächst die Mitarbeiter ab. Liest eine Schleife anschließend employee.department.name, kann Django eine weitere Abfrage ausführen, um die Abteilung zu laden, sofern sie für diese Modellinstanz nicht bereits verfügbar ist.
Wiederholte Zugriffe können zum N+1-Abfragemuster führen: Eine Abfrage ruft die Mitarbeiter ab, danach folgen weitere Abfragen für die jeweils abgerufenen Abteilungen. Die genaue Gesamtzahl hängt davon ab, auf welche Beziehungen zugegriffen wird, ob Objekte bereits geladen oder wiederverwendet werden und wie die übrigen Abfragen ablaufen. Für jede Gruppe von 100 Mitarbeitern eine feste Zahl wie 101 zu versprechen, wäre nicht korrekt. Mehrere Mitarbeiter können derselben Abteilung angehören, und die Anzahl hängt davon ab, wie das QuerySet und seine Objekte verwendet werden.
select_related() in einem QuerySet verwenden
Fügen Sie beim angenommenen Fremdschlüssel den Namen des Beziehungsfeldes zum QuerySet hinzu. In einer Django-View lautet der relevante Ausdruck Employee.objects.all().select_related("department"). Durchlaufen Sie anschließend die zurückgegebenen Mitarbeiter und lesen Sie bei Bedarf employee.department.name. Django ruft die Abteilungsdaten zusammen mit jeder Mitarbeiterzeile über einen SQL-Join ab.
Die Modellbeziehung muss vorhanden sein, und die an select_related() übergebene Zeichenfolge muss ein Beziehungsfeld bezeichnen, nicht ein Anzeigeattribut wie department.name. Bei einem benutzerdefinierten ForeignKey-Feldnamen verwenden Sie den Namen dieses Feldes. Sie können die Methode auch mit anderen QuerySet-Methoden verketten. Da sie ein QuerySet zurückgibt, lassen sich Filter und Sortierung damit kombinieren.
Das folgende eigenständige Python-Beispiel veranschaulicht den Unterschied bei der Anzahl der Abrufe für vier Mitarbeiter und zwei Abteilungen. Es dient nur zur Illustration; es führt weder Django aus noch misst es Datenbankabfragen. Führen Sie dieses Beispiel aus:
Die Bezeichnungen und Ausgaben im unveränderten Beispiel bleiben auf Englisch.
employees = [
{"name": "Jane", "department_id": 1},
{"name": "John", "department_id": 2},
{"name": "Sam", "department_id": 1},
{"name": "Lee", "department_id": 2},
]
departments = {1: "HR", 2: "Accounts"}
separate_lookups = 1 + len(employees)
joined_lookups = 1
print(f"Separate related lookups: {separate_lookups}")
print(f"Related data loaded with employee query: {joined_lookups}")
print(f"First employee's department: {departments[employees[0]['department_id']]}")
Verwenden Sie in der tatsächlichen Anwendung die QuerySet-Methode von Django statt dieser In-Memory-Illustration. Die Illustration zählt für den Fall des getrennten Ladens bewusst einen Abruf der verwandten Daten je Mitarbeiter. Die tatsächliche Abfragezahl kann abweichen, weil Djangos Beziehungscache und das Zugriffsmuster des Codes beeinflussen, welche Daten abgerufen werden.
Abfrageverhalten und Abfragezahlen prüfen
Untersuchen Sie das für ein QuerySet erzeugte SQL über dessen Attribut query, zum Beispiel mit print(Employee.objects.select_related("department").query). So können Sie prüfen, ob die verwandte Tabelle in der Abfrage vorkommt. Angezeigt wird dabei jedoch die SQL-Darstellung und nicht die Anzahl der Abfragen, die während eines gesamten Requests ausgeführt wurden.
Um in einem Entwicklungsprojekt die Abfragen eines Requests zu messen, verwenden Sie Django Debug Toolbar oder Djangos Testwerkzeuge wie assertNumQueries() rund um den zu prüfenden Code. Vergleichen Sie denselben Vorgang vor und nach dem Hinzufügen von select_related() – bei identischem Datenbankinhalt und Zugriffsmuster. Berücksichtigen Sie bei der Auswertung auch den Datenbankaufbau, Middleware und andere Abfragen des Requests. Wenn der Code der ausgewählten Beziehung mit genau einem Objekt folgt, sollten weniger separate Abrufe verwandter Objekte nötig sein. Die Methode garantiert jedoch nicht, dass ein ganzer Request nur eine Abfrage verwendet.
Den passenden Ansatz zum Laden von Beziehungen wählen
select_related() ist für Beziehungen mit genau einem Objekt gedacht: für Pfade über ForeignKey und OneToOneField. Diese verwandten Zeilen werden in die SQL-Abfrage eingebunden. Wenn die entsprechenden Felder in den Modellen vorhanden sind, können Sie mehreren solchen Beziehungen folgen, indem Sie den Pfad angeben, etwa mit select_related("department__manager").
Verwenden Sie für Beziehungen zu Sammlungen, etwa umgekehrte Fremdschlüssel und Many-to-many-Felder, stattdessen prefetch_related(). Dabei werden verwandte Zeilen mit separaten Abfragen abgerufen und in Python zusammengeführt. So vermeiden Sie ein großes Join-Ergebnis mit wiederholten Elterndaten. Wählen Sie den Ansatz passend zur Beziehung und dazu, worauf der Code zugreift: select_related() für die eine Abteilung eines Mitarbeiters und prefetch_related() für eine Sammlung wie die vielen Projekte eines Mitarbeiters. Beide Methoden helfen, ineffizientes wiederholtes Laden zu vermeiden, verwenden aber unterschiedliche Abfragestrategien.