SQLAlchemy: Was ist der Unterschied zwischen flush () und commit ()?

422

Was ist der Unterschied zwischen flush()und commit()in SQLAlchemy?

Ich habe die Dokumente gelesen, bin aber nicht klüger - sie scheinen ein Vorverständnis anzunehmen, das ich nicht habe.

Ich bin besonders an ihren Auswirkungen auf die Speichernutzung interessiert. Ich lade einige Daten aus einer Reihe von Dateien (insgesamt etwa 5 Millionen Zeilen) in eine Datenbank und meine Sitzung fällt gelegentlich um - es ist eine große Datenbank und ein Computer mit wenig Speicher.

Ich frage mich, ob ich zu viele commit()und nicht genug flush()Anrufe verwende - aber ohne wirklich zu verstehen, was der Unterschied ist, ist es schwer zu sagen!

AP257
quelle

Antworten:

533

Ein Sitzungsobjekt ist im Grunde eine laufende Transaktion von Änderungen an einer Datenbank (Aktualisieren, Einfügen, Löschen). Diese Vorgänge werden erst in der Datenbank gespeichert, wenn sie festgeschrieben wurden (wenn Ihr Programm während der Sitzungstransaktion aus irgendeinem Grund abgebrochen wird, gehen alle nicht festgeschriebenen Änderungen in der Datenbank verloren).

Das Sitzungsobjekt registriert Transaktionsoperationen bei session.add(), kommuniziert sie jedoch noch nicht an die Datenbank, bis sie session.flush()aufgerufen wird.

session.flush()kommuniziert eine Reihe von Operationen an die Datenbank (Einfügen, Aktualisieren, Löschen). Die Datenbank verwaltet sie als ausstehende Vorgänge in einer Transaktion. Die Änderungen werden nicht dauerhaft auf der Festplatte gespeichert oder sind für andere Transaktionen sichtbar, bis die Datenbank ein COMMIT für die aktuelle Transaktion erhält (was auch der session.commit()Fall ist).

session.commit() Übernimmt diese Änderungen in die Datenbank.

flush()wird immer als Teil eines Aufrufs von commit()( 1 ) aufgerufen .

Wenn Sie ein Sitzungsobjekt zum Abfragen der Datenbank verwenden, gibt die Abfrage Ergebnisse sowohl aus der Datenbank als auch aus den gelöschten Teilen der darin nicht festgeschriebenen Transaktion zurück. Standardmäßig widerspricht Session autoflushihren Vorgängen, dies kann jedoch deaktiviert werden.

Hoffentlich wird dieses Beispiel dies klarer machen:

#---
s = Session()

s.add(Foo('A')) # The Foo('A') object has been added to the session.
                # It has not been committed to the database yet,
                #   but is returned as part of a query.
print 1, s.query(Foo).all()
s.commit()

#---
s2 = Session()
s2.autoflush = False

s2.add(Foo('B'))
print 2, s2.query(Foo).all() # The Foo('B') object is *not* returned
                             #   as part of this query because it hasn't
                             #   been flushed yet.
s2.flush()                   # Now, Foo('B') is in the same state as
                             #   Foo('A') was above.
print 3, s2.query(Foo).all() 
s2.rollback()                # Foo('B') has not been committed, and rolling
                             #   back the session's transaction removes it
                             #   from the session.
print 4, s2.query(Foo).all()

#---
Output:
1 [<Foo('A')>]
2 [<Foo('A')>]
3 [<Foo('A')>, <Foo('B')>]
4 [<Foo('A')>]
Snapshoe
quelle
Nur noch eins: Wissen Sie, ob der Aufruf von commit () den verwendeten Speicher erhöht oder verringert?
AP257
2
Dies gilt auch für DB-Engines, die keine Transaktionen wie myisam unterstützen. Da keine Transaktion ausgeführt wird, muss sich Flush noch weniger vom Commit unterscheiden.
Unterlauf
1
@underrun Wenn ich es session.query() danach mache session.flush(), werde ich meine Änderungen sehen? Vorausgesetzt, ich benutze MyISAM.
Gefrorene Flamme
1
Ist es gut oder schlecht zu benutzen flush()und commit()oder sollte ich das der Alchemie überlassen? Ich habe es flush()in einigen Fällen verwendet, weil nachfolgende Abfragen neue Daten abrufen mussten.
Jens
1
@Jens Use autoflush( Truestandardmäßig). Es wird automatisch vor allen Abfragen gelöscht, sodass Sie sich nicht jedes Mal daran erinnern müssen.
Kiran Jonnalagadda
24

Wie @snapshoe sagt

flush() sendet Ihre SQL-Anweisungen an die Datenbank

commit() schreibt die Transaktion fest.

Wann session.autocommit == False:

commit()wird anrufen, flush()wenn Sie einstellen autoflush == True.

Wann session.autocommit == True:

Sie können nicht anrufen, commit()wenn Sie keine Transaktion gestartet haben (was Sie wahrscheinlich nicht getan haben, da Sie diesen Modus wahrscheinlich nur verwenden würden, um die manuelle Verwaltung von Transaktionen zu vermeiden).

In diesem Modus müssen Sie anrufen flush(), um Ihre ORM-Änderungen zu speichern. Der Flush schreibt effektiv auch Ihre Daten fest.

Jacob
quelle
24
"commit () ruft flush () auf, wenn Ihr Autoflush == True ist." ist nicht ganz richtig oder nur irreführend. Commit spült immer, unabhängig von der Einstellung für das automatische Spülen.
Ilja Everilä
3
Der autoflushParameter steuert, ob sqlalchemy zuerst einen Flush ausgibt, wenn vor dem Ausgeben einer Abfrage ausstehende Schreibvorgänge vorhanden sind, und hat nichts mit der Steuerung des unvermeidlichen Flushs beim Festschreiben zu tun.
SuperShoot
4

Warum spülen, wenn Sie sich verpflichten können?

Als jemand, der neu in der Arbeit mit Datenbanken und SQLalchemie ist, waren mir die vorherigen Antworten, die flush()SQL-Anweisungen an die Datenbank senden und diese beibehalten, commit()nicht klar. Die Definitionen sind sinnvoll, aber aus den Definitionen geht nicht sofort hervor, warum Sie einen Flush verwenden würden, anstatt nur einen Commit durchzuführen.

Da ein Commit immer gelöscht wird ( https://docs.sqlalchemy.org/en/13/orm/session_basics.html#committing ), klingen diese sehr ähnlich. Ich denke, das große Problem ist, dass ein Flush nicht permanent ist und rückgängig gemacht werden kann, während ein Commit permanent ist, in dem Sinne, dass Sie die Datenbank nicht bitten können, das letzte Commit rückgängig zu machen (glaube ich).

@snapshoe hebt hervor, dass Sie, wenn Sie die Datenbank abfragen und Ergebnisse mit neu hinzugefügten Objekten erhalten möchten, zuerst geleert haben müssen (oder festgeschrieben haben müssen, was für Sie geleert wird). Vielleicht ist dies für einige Leute nützlich, obwohl ich nicht sicher bin, warum Sie lieber spülen als festschreiben möchten (abgesehen von der trivialen Antwort, dass es rückgängig gemacht werden kann).

In einem anderen Beispiel habe ich Dokumente zwischen einer lokalen Datenbank und einem Remote-Server synchronisiert. Wenn der Benutzer den Vorgang abbrechen möchte, sollten alle Hinzufügungen / Aktualisierungen / Löschungen rückgängig gemacht werden (dh keine teilweise Synchronisierung, nur eine vollständige Synchronisierung). Beim Aktualisieren eines einzelnen Dokuments habe ich beschlossen, einfach die alte Zeile zu löschen und die aktualisierte Version vom Remote-Server hinzuzufügen. Es stellt sich heraus, dass aufgrund der Art und Weise, wie sqlalchemy geschrieben ist, die Reihenfolge der Operationen beim Festschreiben nicht garantiert ist. Dies führte dazu, dass eine doppelte Version hinzugefügt wurde (bevor versucht wurde, die alte zu löschen), was dazu führte, dass die Datenbank eine eindeutige Einschränkung nicht erfüllte. Um dies zu umgehen, habe ich verwendet, flush()damit die Reihenfolge beibehalten wurde, aber ich konnte es trotzdem rückgängig machen, wenn der Synchronisierungsprozess später fehlschlug.

Siehe meinen Beitrag dazu unter: Gibt es eine Reihenfolge für das Hinzufügen oder Löschen beim Festschreiben in sqlalchemy?

In ähnlicher Weise wollte jemand wissen, ob beim Hinzufügen eine Reihenfolge hinzugefügt wird, dh wenn ich hinzufüge und object1dann hinzufüge object2, wird object1sie der Datenbank hinzugefügt, bevor object2 SQLAlchemy die Reihenfolge beim Hinzufügen von Objekten zur Sitzung speichert.

Auch hier würde vermutlich die Verwendung eines Flush () das gewünschte Verhalten sicherstellen. Zusammenfassend lässt sich sagen, dass eine Verwendung für Flush darin besteht, Bestellgarantien bereitzustellen (glaube ich), während Sie sich dennoch eine "Rückgängig" -Option erlauben, die Commit nicht bietet.

Autoflush und Autocommit

Beachten Sie, dass das automatische Löschen verwendet werden kann, um sicherzustellen, dass Abfragen in einer aktualisierten Datenbank ausgeführt werden, da sqlalchemy vor dem Ausführen der Abfrage geleert wird. https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.params.autoflush

Autocommit ist etwas anderes, das ich nicht vollständig verstehe, aber es scheint, als würde von seiner Verwendung abgeraten: https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.params. Autocommit

Speichernutzung

Nun wollte die ursprüngliche Frage eigentlich wissen, wie sich Flush vs. Commit für Speicherzwecke auswirkt. Da die Fähigkeit zum Fortbestehen oder Nichtbestehen etwas ist, was die Datenbank bietet (glaube ich), sollte ein einfaches Leeren ausreichen, um in die Datenbank zu verlagern - obwohl das Festschreiben nicht schaden sollte (was wahrscheinlich hilft - siehe unten), wenn Sie sich nicht um das Rückgängigmachen kümmern .

sqlalchemy verwendet eine schwache Referenzierung für Objekte, die gelöscht wurden: https://docs.sqlalchemy.org/en/13/orm/session_state_management.html#session-referencing-behavior

Das heißt, wenn Sie ein Objekt nicht explizit an einem Ort haben, wie in einer Liste oder einem Diktat, wird es von sqlalchemy nicht gespeichert.

Dann müssen Sie sich jedoch um die Datenbankseite kümmern. Vermutlich ist das Löschen ohne Festschreiben mit einer Speicherstrafe verbunden, um die Transaktion aufrechtzuerhalten. Auch hier bin ich neu, aber hier ist ein Link, der genau dies zu suggerieren scheint: https://stackoverflow.com/a/15305650/764365

Mit anderen Worten, Commits sollten die Speichernutzung reduzieren, obwohl hier vermutlich ein Kompromiss zwischen Speicher und Leistung besteht. Mit anderen Worten, Sie möchten wahrscheinlich nicht jede einzelne Datenbankänderung einzeln festschreiben (aus Leistungsgründen), aber zu langes Warten erhöht die Speichernutzung.

Jimbo
quelle
1

Dies beantwortet nicht unbedingt die ursprüngliche Frage, aber einige Leute haben erwähnt, dass session.autoflush = TrueSie nicht verwenden müssen session.flush()... Und das ist nicht immer wahr.

Wenn Sie die ID eines neu erstellten Objekts mitten in einer Transaktion verwenden möchten , müssen Sie aufrufen session.flush().

# Given a model with at least this id
class AModel(Base):
   id = Column(Integer, primary_key=True)  # autoincrement by default on integer primary key

session.autoflush = True

a = AModel()
session.add(a)
a.id  # None
session.flush()
a.id  # autoincremented integer

Dies liegt daran , dass die ID NICHT automatisch ausgefüllt wird (obwohl eine Abfrage des Objekts dies autoflushtut , was manchmal zu Verwirrung führen kann, wie in "Warum funktioniert das hier, aber nicht dort?", Aber Snapshoe hat diesen Teil bereits behandelt).


Ein verwandter Aspekt, der mir ziemlich wichtig erscheint und nicht wirklich erwähnt wurde:

Warum würden Sie nicht die ganze Zeit begehen? - Die Antwort ist Atomizität .

Ein schickes Wort: Ein Ensemble von Operationen muss alle erfolgreich ausgeführt werden, oder keine von ihnen wird wirksam.

Wenn Sie beispielsweise ein Objekt (A) erstellen / aktualisieren / löschen und dann ein anderes Objekt (B) erstellen / aktualisieren / löschen möchten, aber (B) fehlschlagen möchten, möchten Sie (A) zurücksetzen. Dies bedeutet, dass diese beiden Operationen atomar sind .

Wenn (B) ein Ergebnis von (A) benötigt, möchten Sie daher flushnach (A) und commitnach (B) aufrufen .

Außerdem müssen Sie nicht manuell anrufen session.autoflush is True, außer in dem Fall, den ich oben oder in Jimbos Antwort erwähnt habe flush.

Romain Vincent
quelle