Sqlalchemy - Unterschied zwischen query und query.all in for-Schleifen

74

Ich möchte fragen, was der Unterschied zwischen ist

for row in session.Query(Model1):
    pass

und

for row in session.Query(Model1).all():
    pass

Ist der erste irgendwie ein Iterator, der Ihre Datenbank mit einzelnen Abfragen bombardiert, und der letztere "eifrig" fragt das Ganze als Liste ab (wie range (x) vs xrange (x))?

Tom
quelle

Antworten:

99

Nein, es gibt keinen Unterschied im DB-Verkehr. Der Unterschied besteht nur darin, dass for row in session.Query(Model1)das ORM in jeder Zeile funktioniert, wenn es Ihnen übergeben werden soll, während for row in session.Query(Model1).all()das ORM in allen Zeilen funktioniert, bevor es Ihnen gegeben wird.

Beachten Sie, dass dies q.all()nur Zucker ist list(q), dh alles, was der Generator liefert, in einer Liste zu sammeln. Hier ist der Quellcode dafür in der QueryKlasse (finden Sie def allin der verknüpften Quelle):

def all(self):
    """Return the results represented by this ``Query`` as a list.

    This results in an execution of the underlying query.

    """
    return list(self)

... wobei selfdas Abfrageobjekt iterierbar ist, dh eine __iter__Methode hat.

Logischerweise sind die beiden Möglichkeiten in Bezug auf den DB-Verkehr genau gleich. Am Ende rufen beide query.__iter__()an, um einen Zeileniterator zu erhalten, und arbeiten next()sich durch diesen.

Der praktische Unterschied ist , dass der ehemalige kann beginnen Sie Zeilen , sobald ihre Daten angekommen geben „Streaming“ der DB Ergebnismenge zu Ihnen, mit weniger Speicherverbrauch und Latenz. Ich kann nicht sicher sagen, dass alle aktuellen Engine-Implementierungen dies tun (ich hoffe, dass sie es tun!). In jedem Fall verhindert die letztere Version diese Effizienz ohne guten Grund.

Gunnlaugur Briem
quelle
13
Dieser Beitrag beleuchtet die Implementierungsdetails. Kurze Antwort: __iter__ tut alle Ergebnisse Prefetch (aus gutem Grund), aber dieses Verhalten kann geändert werden , wenn Sie wissen , was Sie tun.
Coquelicot
Es gibt also keinen Vorteil bei der Verwendung q.all()?
dshgna
2
Nun, es ist etwas besser lesbar als list(q), denke ich. Verhalten und Leistung sind jedoch gleich. Und das Wiederholen q.all()ist zumindest potenziell langsamer zu starten und speicherhungriger als das Wiederholen q.
Gunnlaugur Briem
Der Datenbanktreffer ist möglicherweise derselbe. Beachten Sie jedoch, dass alldie gesamte Ergebnismenge in den Speicher geladen wird. Dies können Gigabyte an Daten sein.
Lukas Batteau
11

Tatsächlich ist die akzeptierte Antwort nicht wahr (oder zumindest nicht mehr, wenn überhaupt wahr), insbesondere sind die folgenden Aussagen falsch:

(1) Der Unterschied besteht nur darin, dass für die Zeile in der Sitzung.Query (Modell1) das ORM für jede Zeile ausgeführt wird, wenn es Ihnen übergeben wird, während für die Zeile in der Sitzung.Query (Modell1) .all () die Funktion ausführt ORM arbeitet an allen Zeilen, bevor Sie sie Ihnen geben.

SQLAlchemy ordnet immer alle Zeilen ORM zu, unabhängig davon, welche der beiden Optionen Sie verwenden. Dies kann in ihrem Quellcode innerhalb dieser Zeilen gesehen werden ; Die loading.instancesMethode gibt zwar einen Generator zurück, aber eine der bereits zugeordneten ORM-Instanzen. Sie können dies im eigentlichen Generatorschleifencode bestätigen :

for row in rows: #  ``rows`` here are already ORM mapped rows
    yield row

Bis der erste Lauf des Generators beendet ist und eine Instanz ausgegeben wurde, wurden alle Instanzen ORM-zugeordnet. (Nachstehend beispielhaft dargestellt (1))

(2) Der praktische Unterschied besteht darin, dass erstere Ihnen Zeilen geben können, sobald ihre Daten eingegangen sind, und die DB-Ergebnismenge mit weniger Speicherbedarf und Latenz „streamen“.

Wie oben erläutert, ist dies auch falsch, da alle aus der Datenbank abgerufenen Daten verarbeitet / zugeordnet werden, bevor eine Nachgiebigkeit erfolgt.

Die Kommentare sind auch irreführend. Speziell:

Der Datenbanktreffer ist möglicherweise derselbe. Beachten Sie jedoch, dass alle die gesamte Ergebnismenge in den Speicher laden. Dies können Gigabyte an Daten sein

Die gesamte Ergebnismenge wird unabhängig von der Verwendung geladen .all()oder nicht. In dieser Zeile deutlich zu sehen . Dies ist auch sehr einfach zu testen / zu verifizieren.

Der einzige Unterschied, den ich aus den beiden angegebenen Optionen erkennen kann, besteht darin, dass Sie bei Verwendung .allzweimal die Ergebnisse durchlaufen (wenn Sie alle Instanzen / Zeilen verarbeiten möchten), da der Generator zuerst durch den Aufruf iteriert / erschöpft wird, in den list(self)er transformiert werden soll eine Liste.

Da der SQLAlchemy-Code nicht leicht zu verdauen ist, habe ich einen kurzen Ausschnitt geschrieben, um all dies zu veranschaulichen:

class Query:
    def all(self):
        return list(self)

    def instances(self, rows_to_fetch=5):
        """ORM instance generator"""
        mapped_rows = []
        for i in range(rows_to_fetch):
            # ORM mapping work here as in lines 81-88 from loading.instances
            mapped_rows.append(i)
        print("orm work finished for all rows")
        for row in mapped_rows:  # same as ``yield from mapped_rows``
            print("yield row")
            yield row

    def __iter__(self):
        return self.instances()


query = Query()
print("(1) Generator scenario:")
print("First item of generator: ", next(iter(query)))

print("\n(2) List scenario:")
print("First item of list: ", query.all()[0])

"""
RESULTS:
--------
(1) Generator scenario:
orm work finished for all rows
yield row
First item of generator:  0

(2) List scenario:
orm work finished for all rows
yield row
yield row
yield row
yield row
yield row
First item of list:  0
"""

Um eine feinere Kontrolle bei der Verarbeitung großer Ergebnismengen zu haben, müsste beispielsweise so etwas wie Yield_per verwendet werden, und dennoch wäre dies kein Einzelszenario , sondern die ORM-Zuordnung nach mehreren Instanzen.

Erwähnenswert ist der einzige Kommentar , der tatsächlich zutreffend war und leicht übersehen werden kann (daher das Schreiben dieser Antwort) und auf eine Erklärung von Michael Bayer hinweist .

Gera Zenobi
quelle