it-swarm.com.de

Sollte ich die (konkrete) Vererbung mehrerer Tabellen in Django auf keinen Fall vermeiden?

Viele erfahrene Entwickler empfehlen, Django Multi-Table-Vererbung wegen seiner schlechten Leistung nicht zu verwenden:

  1. Django gotcha: konkretes Erbe von Jacob Kaplan-Moss , einem der wichtigsten Mitwirkenden von Django.

    In fast allen Fällen ist die abstrakte Vererbung auf lange Sicht ein besserer Ansatz. Ich habe mehr als nur wenige Websites gesehen, die unter der Last der Betonvererbung zermalmt wurden. Daher würde ich den Django-Nutzern dringend empfehlen, jede Verwendung der Betonvererbung mit einer großen Dosis Skepsis anzugehen.

  2. Zwei Kugeln Django von Daniel Greenfield ( @pydanny )

    Die Vererbung mehrerer Tabellen, manchmal auch als "konkrete Vererbung" bezeichnet, wird von den Autoren und vielen anderen Entwicklern als eine schlechte Sache angesehen. Wir empfehlen dringend davon abzuraten.

    Jeder sollte auf jeden Fall die Vererbung mehrerer Tabellen vermeiden, da dies zu Verwirrung und erheblichem Overhead führt. Verwenden Sie anstelle der Vererbung mehrerer Tabellen explizite OneToOneFields und ForeignKeys zwischen den Modellen, damit Sie steuern können, wann Verknüpfungen durchlaufen werden.

Aber ohne Multi-Table-Vererbung kann ich nicht einfach

  1. Referenzbasismodell in einem anderen Modell (muss GenericForeignKey oder umgekehrte Abhängigkeit verwenden);

  2. Holen Sie sich alle Instanzen des Basismodells .

    (Sie können gerne weitere hinzufügen)

Was ist also los mit dieser Art von Erbschaft in Django? Warum sind explizite OneToOneFields besser?

Wie sehr leidet die Leistung unter JOINs? Gibt es Benchmarks, die den Leistungsunterschied anzeigen?

Ermöglicht uns select_related() nicht zu steuern, wann JOINs aufgerufen werden?


Ich habe konkrete Beispiele auf eine separate Frage verschoben, da diese zu umfangreich wird, und stattdessen eine Liste mit Gründen für die Verwendung der Mehrtabellenvererbung hinzugefügt.

33
utapyngo

Erstens hat inheritance keine natürliche Übersetzung in eine relationale Datenbankarchitektur (ok, ich weiß, Oracle Type Objects und einige andere RDBMS unterstützen die Vererbung, aber Django nutzt diese Funktionalität nicht)

Beachten Sie, dass Django neue Tabellen für Unterklassen generiert und viele left joinsschreibt, um Daten aus diesen 'Untertabellen' abzurufen. Und Left Joins sind nicht deine Freunde . In einem Hochleistungsszenario, wie einem Spiel-Backend oder etwas anderem, sollten Sie dies vermeiden und die Vererbung 'von Hand' mit einigen Artefakten wie Nullen, OneToOne oder Fremdschlüsseln auflösen. Im OneToOne-Szenario können Sie die verknüpfte Tabelle direkt oder nur bei Bedarf aufrufen.

... ABER ...

"Meiner Meinung nach (TGW)" Sie sollten die Modellvererbung in Ihre Unternehmensprojekte einbeziehen, wenn sie in Ihr Diskursuniversum passt. Ich mache das und spare meinen Kunden dank dieser Funktion eine Menge Entwicklungsstunden. Auch Code wird sauber und elegant und das bedeutet einfache Wartung (beachten Sie, dass diese Art von Projekten nicht hunderte oder sekundenweise Anfragen haben)

Frage für Frage

F: Was ist los mit dieser Art von Vererbung in Django?
A: Viele Tische, viele linke Joins.

F: Warum sind explizite OneToOneFields besser?
A: Sie können direkt auf ein verwandtes Modell zugreifen, ohne Linksverbindungen.

F: Gibt es illustrative Beispiele (Benchmarks)?
A: Nicht vergleichbar.

F: Ermöglicht uns select_related () nicht zu steuern, wann JOINs aufgerufen werden?
A: Django schließt sich den benötigten Tischen an.

F: Welche Alternativen zur Mehrtabellenvererbung gibt es, wenn ich in einem anderen Modell auf eine Basisklasse verweisen muss?
A: Aufhebung. OneToOne-Beziehungen und viele Codezeilen. Dies hängt von den Anwendungsanforderungen ab.

F: Sind GenericForeignKeys in diesem Fall besser?
A: Nein für mich.

F: Was ist, wenn ich OneToOneField als Basismodell benötige? A: Schreib es. Das ist kein Problem. Beispielsweise können Sie das Benutzermodell erweitern und für einige Benutzer ein OneToOne-zu-Benutzer-Basismodell einrichten.

Fazit

Sie sollten die Kosten für Schreib- und Wartungscode ohne Modellvererbung kennen und auch die Kosten für Hardware, um Modellvererbungsanwendungen zu unterstützen und entsprechend zu handeln.

22
dani herrera

Soweit ich weiß, verwenden Sie OneToOneField für die RelatedModel für die BaseModel, da Sie letztendlich eine Eins-zu-Eins-Verknüpfung zwischen RelatedModel und jedem Submodel1 für Submodel9 wünschen. Wenn ja, gibt es eine effizientere Möglichkeit, ohne Mehrtabellenvererbung oder generische Beziehungen.

Werde einfach die BaseModel los und habe in jeder SubmodelX eine OneToOneField zu RelatedModel

class Submodel1(models.Model):
    related_model = models.OneToOneField(RelatedModel, null=True, blank=True, related_name='the_thing')
    some_field = models.TextField()

# ...

class Submodel9(models.Model):
    related_model = models.OneToOneField(RelatedModel, null=True, blank=True, related_name='the_thing')
    another_field = models.TextField()

Auf diese Weise können Sie von einer Instanz von SubmodelX über ein Feld mit dem Namen the_thing auf RelatedModel zugreifen, genau wie im Beispiel für die Vererbung mehrerer Tabellen, das Sie zuerst angegeben haben.

Beachten Sie, dass Sie die abstrakte Vererbung verwenden können, um das Feld related_model und alle anderen gemeinsamen Felder zwischen SubModel1 und Submodel9 herauszufiltern.

Die Verwendung der Mehrtabellenvererbung ist ineffizient, weil dadurch eine zusätzliche Tabelle für das Basismodell und daher zusätzliche JOINs für den Zugriff auf diese Felder generiert werden. Die Verwendung generischer Beziehungen ist effizienter, wenn Sie später feststellen, dass Sie stattdessen ein ForeignKey-Feld von RelatedModel für jede SubmodelX benötigen. Django unterstützt jedoch keine generischen Beziehungen in select_related() und Sie müssen möglicherweise Ihre eigenen Abfragen erstellen, um dies effizient zu tun. Der Kompromiss zwischen Leistung und einfacher Codierung liegt bei Ihnen, je nachdem, wie viel Last Sie auf dem Server erwarten und wie viel Zeit Sie für die Optimierung aufwenden möchten.

11
user193130

Die Welt hat sich verändert.

Das erste, was zu bemerken ist, ist, dass der Artikel mit dem Titel Django gotcha: konkretes Erbe zu dem Zeitpunkt, als diese Frage gestellt wurde, fast vier Jahre alt war; Sowohl Django- als auch RDBM-Systeme haben seitdem einen langen Weg zurückgelegt (Beispiel: mysql 5.0 oder 5.1 waren die weit verbreiteten Versionen, und die allgemeine Verfügbarkeit von 5.5 war noch einen Monat entfernt).

Verbindet sich zu meiner Linken, verbindet sich zu meiner Rechten

Es ist richtig, dass die Vererbung mehrerer Tabellen hinter den Kulissen zu zusätzlichen Verknüpfungen führt meistens . Aber Verbindungen sind nicht böse. Es ist erwähnenswert, dass Sie in einer ordnungsgemäß normalisierten Datenbank fast immer beitreten müssen, um alle erforderlichen Daten abzurufen. Bei Verwendung geeigneter Indizes beinhalten Joins keine wesentlichen Leistungseinbußen.

INNER JOIN vs LEFT OUTER JOIN

Dies ist in der Tat der Fall bei der Vererbung mehrerer Tabellen. Bei anderen Ansätzen ist es möglich, einen kostspieligen LEFT OUTER JOIN zu vermeiden und stattdessen einen INNER JOIN oder möglicherweise eine Unterabfrage durchzuführen. Bei der Vererbung mehrerer Tabellen wird Ihnen diese Auswahl jedoch verweigert

5
e4c5

Django implementiert die Vererbung mehrerer Tabellen über ein automatisch erstelltes OneToOneField, wie in den Dokumenten angegeben. Verwenden Sie also entweder die abstrakte Vererbung, oder ich denke nicht, dass die Verwendung eines expliziten OneToOneFields oder ForeignKeys Unterschiede macht.

0
youngjack

Ob das Auftreten von LEFT OUTER JOIN ein Problem für sich ist, kann ich nicht sagen, aber auf jeden Fall kann es interessant sein, in welchen Fällen diese äußeren Verknüpfungen tatsächlich auftreten zu notieren.

Dies ist ein naiver Versuch, das oben Gesagte anhand einiger Beispielabfragen zu veranschaulichen.

Angenommen, wir haben einige Modelle mit Mehrtabellenvererbung wie folgt:

from Django.db import models

class Parent(models.Model):
    parent_field = models.CharField(max_length=10)


class ChildOne(Parent):
    child_one_field = models.CharField(max_length=10)


class ChildTwo(Parent):
    child_two_field = models.CharField(max_length=10)

Standardmäßig erhalten die untergeordneten Instanzen einen parent_ptr und übergeordnete Instanzen können mit childone oder childtwo auf untergeordnete Objekte zugreifen (sofern vorhanden). Beachten Sie, dass parent_ptr eine Eins-zu-Eins-Beziehung darstellt, die als Primärschlüssel verwendet wird (die tatsächlichen untergeordneten Tabellen haben keine Spalte id).

Hier ist ein schneller und unsauberer Unit-Test mit einigen naiven Beispielen für Django-Abfragen, in denen die entsprechende Anzahl der Vorkommen von INNER JOIN und OUTER JOIN in der SQL angegeben ist:

import re
from Django.test import TestCase
from inheritance.models import (Parent, ChildOne, ChildTwo)

def count_joins(query, inner_outer):
    """ Count the occurrences of JOIN in the query """
    return len(re.findall('{} join'.format(inner_outer), str(query).lower()))


class TestMultiTableInheritance(TestCase):
    def test_queries(self):
        # get children (with parent info)
        query = ChildOne.objects.all().query
        self.assertEqual(1, count_joins(query, 'inner'))
        self.assertEqual(0, count_joins(query, 'outer'))
        # get parents
        query = Parent.objects.all().query
        self.assertEqual(0, count_joins(query, 'inner'))
        self.assertEqual(0, count_joins(query, 'outer'))
        # filter children by parent field
        query = ChildOne.objects.filter(parent_field=parent_value).query
        self.assertEqual(1, count_joins(query, 'inner'))
        self.assertEqual(0, count_joins(query, 'outer'))
        # filter parents by child field
        query = Parent.objects.filter(childone__child_one_field=child_value).query
        self.assertEqual(1, count_joins(query, 'inner'))
        self.assertEqual(0, count_joins(query, 'outer'))
        # get child field values via parent
        query = Parent.objects.values_list('childone__child_one_field').query
        self.assertEqual(0, count_joins(query, 'inner'))
        self.assertEqual(1, count_joins(query, 'outer'))
        # get multiple child field values via parent
        query = Parent.objects.values_list('childone__child_one_field',
                                           'childtwo__child_two_field').query
        self.assertEqual(0, count_joins(query, 'inner'))
        self.assertEqual(2, count_joins(query, 'outer'))
        # get child-two field value from child-one, through parent
        query = ChildOne.objects.values_list('parent_ptr__childtwo__child_two_field').query
        self.assertEqual(1, count_joins(query, 'inner'))
        self.assertEqual(1, count_joins(query, 'outer'))
        # get parent field value from parent, but through child
        query = Parent.objects.values_list('childone__parent_field').query
        self.assertEqual(0, count_joins(query, 'inner'))
        self.assertEqual(2, count_joins(query, 'outer'))
        # filter parents by parent field, but through child
        query = Parent.objects.filter(childone__parent_field=parent_value).query
        self.assertEqual(2, count_joins(query, 'inner'))
        self.assertEqual(0, count_joins(query, 'outer'))

Beachten Sie, dass nicht alle dieser Abfragen sinnvoll sind: Sie dienen nur zur Veranschaulichung.

Beachten Sie auch, dass dieser Testcode nicht TROCKEN ist, sondern absichtlich.

0
djvg