Die betreffende Tabelle enthält ungefähr zehn Millionen Zeilen.
for event in Event.objects.all():
print event
Dies führt dazu, dass die Speichernutzung stetig auf etwa 4 GB ansteigt. Zu diesem Zeitpunkt werden die Zeilen schnell gedruckt. Die lange Verzögerung vor dem Drucken der ersten Zeile hat mich überrascht - ich hatte erwartet, dass sie fast sofort gedruckt wird.
Ich habe auch versucht, Event.objects.iterator()
was sich genauso verhält.
Ich verstehe nicht, was Django in den Speicher lädt oder warum es dies tut. Ich hatte erwartet, dass Django die Ergebnisse auf Datenbankebene durchläuft, was bedeuten würde, dass die Ergebnisse mit einer ungefähr konstanten Rate gedruckt werden (und nicht alle auf einmal nach einer langen Wartezeit).
Was habe ich falsch verstanden?
(Ich weiß nicht, ob es relevant ist, aber ich verwende PostgreSQL.)
quelle
Antworten:
Nate C war nah dran, aber nicht ganz.
Aus den Dokumenten :
Ihre zehn Millionen Zeilen werden also auf einmal abgerufen, wenn Sie diese Schleife zum ersten Mal betreten und die iterierende Form des Abfragesatzes erhalten. Das Warten, das Sie erleben, ist, dass Django die Datenbankzeilen lädt und Objekte für jede erstellt, bevor er etwas zurückgibt, über das Sie tatsächlich iterieren können. Dann haben Sie alles im Gedächtnis und die Ergebnisse kommen heraus.
Beim Lesen der Dokumente wird
iterator()
lediglich die internen Caching-Mechanismen von QuerySet umgangen. Ich denke, es könnte sinnvoll sein, eins nach dem anderen zu tun, aber das würde umgekehrt zehn Millionen einzelne Treffer in Ihrer Datenbank erfordern. Vielleicht gar nicht so wünschenswert.Das effiziente Durchlaufen großer Datenmengen ist immer noch nicht ganz richtig, aber es gibt einige Ausschnitte, die Sie für Ihre Zwecke nützlich finden könnten:
quelle
Könnte nicht die schnellere oder effizienteste sein, aber als fertige Lösung können Sie die hier dokumentierten Paginator- und Page-Objekte von django core verwenden:
https://docs.djangoproject.com/de/dev/topics/pagination/
Etwas wie das:
quelle
Paginator
hat jetzt einepage_range
Eigenschaft, um Boilerplate zu vermeiden. Wenn Sie auf der Suche nach minimalem Speicheraufwand sind, können Sie verwenden,object_list.iterator()
wodurch der Abfragesatz-Cache nicht gefüllt wird .prefetch_related_objects
wird dann für den Prefetch benötigtDas Standardverhalten von Django besteht darin, das gesamte Ergebnis des QuerySet zwischenzuspeichern, wenn die Abfrage ausgewertet wird. Sie können die Iterator-Methode von QuerySet verwenden, um dieses Caching zu vermeiden:
https://docs.djangoproject.com/de/dev/ref/models/querysets/#iterator
Die iterator () -Methode wertet das Abfrageset aus und liest die Ergebnisse direkt, ohne das Caching auf QuerySet-Ebene durchzuführen. Diese Methode führt zu einer besseren Leistung und einer erheblichen Reduzierung des Arbeitsspeichers, wenn Sie über eine große Anzahl von Objekten iterieren, auf die Sie nur einmal zugreifen müssen. Beachten Sie, dass das Caching weiterhin auf Datenbankebene erfolgt.
Die Verwendung von iterator () reduziert die Speichernutzung für mich, ist aber immer noch höher als erwartet. Die Verwendung des von mpaf vorgeschlagenen Paginator-Ansatzes verbraucht viel weniger Speicher, ist jedoch für meinen Testfall 2-3x langsamer.
quelle
Dies ist aus den Dokumenten: http://docs.djangoproject.com/en/dev/ref/models/querysets/
Wenn das ausgeführt
print event
wird, wird die Abfrage ausgelöst (dies ist ein vollständiger Tabellenscan gemäß Ihrem Befehl) und lädt die Ergebnisse. Sie fragen nach allen Objekten und es gibt keine Möglichkeit, das erste Objekt zu erhalten, ohne alle zu erhalten.Aber wenn Sie etwas tun wie:
http://docs.djangoproject.com/de/dev/topics/db/queries/#limiting-querysets
Dann werden dem SQL intern Offsets und Limits hinzugefügt.
quelle
Bei großen Mengen an Datensätzen ist ein Datenbankcursor noch leistungsfähiger. In Django benötigen Sie unformatiertes SQL. Der Django-Cursor unterscheidet sich von einem SQL-Cursor.
Die von Nate C vorgeschlagene LIMIT-OFFSET-Methode ist möglicherweise gut genug für Ihre Situation. Bei großen Datenmengen ist es langsamer als ein Cursor, da immer wieder dieselbe Abfrage ausgeführt werden muss und immer mehr Ergebnisse übersprungen werden müssen.
quelle
Django hat keine gute Lösung, um große Objekte aus der Datenbank abzurufen.
values_list kann verwendet werden, um alle IDs in den Datenbanken abzurufen und dann jedes Objekt separat abzurufen. Im Laufe der Zeit werden große Objekte im Speicher erstellt und kein Müll gesammelt, bis die Schleife beendet wird. Der obige Code führt eine manuelle Speicherbereinigung durch, nachdem jeder 100. Artikel verbraucht wurde.
quelle
Auf diese Weise werden Objekte für einen gesamten Abfragesatz auf einmal in den Speicher geladen. Sie müssen Ihr Abfrageset in kleinere verdauliche Teile aufteilen. Das Muster dafür heißt Löffelfütterung. Hier ist eine kurze Implementierung.
Um dies zu verwenden, schreiben Sie eine Funktion, die Operationen an Ihrem Objekt ausführt:
und dann führen Sie diese Funktion auf Ihrem Abfrageset aus:
Dies kann durch
func
Mehrfachverarbeitung weiter verbessert werden, um mehrere Objekte parallel auszuführen .quelle
Hier eine Lösung einschließlich len und count:
Verwendung:
quelle
Normalerweise verwende ich für diese Art von Aufgabe eine rohe MySQL-Rohabfrage anstelle von Django ORM.
MySQL unterstützt den Streaming-Modus, sodass wir alle Datensätze sicher und schnell ohne Speicherfehler durchlaufen können.
Ref:
quelle
queryset.query
für in Ihrer Ausführung.