SQLAlchemy: qual è la differenza tra flush () e commit ()?


422

Qual è la differenza tra flush()e commit()in SQLAlchemy?

Ho letto i documenti, ma non sono affatto il più saggio - sembrano assumere una pre-comprensione che non ho.

Sono particolarmente interessato al loro impatto sull'utilizzo della memoria. Sto caricando alcuni dati in un database da una serie di file (circa 5 milioni di righe in totale) e la mia sessione a volte cade - è un database di grandi dimensioni e una macchina con poca memoria.

Mi chiedo se sto usando troppe commit()e non abbastanza flush()chiamate, ma senza capire davvero quale sia la differenza, è difficile dirlo!

Risposte:


534

Un oggetto Session è sostanzialmente una transazione in corso di modifiche a un database (aggiornamento, inserimento, eliminazione). Queste operazioni non vengono mantenute nel database fino a quando non vengono eseguite (se il programma si interrompe per qualche motivo nella transazione a metà sessione, tutte le modifiche non confermate all'interno vanno perse).

L'oggetto sessione registra le operazioni di transazione session.add(), ma non le comunica ancora al database fino a quando non session.flush()viene chiamato.

session.flush()comunica una serie di operazioni al database (inserisci, aggiorna, cancella). Il database li mantiene come operazioni in sospeso in una transazione. Le modifiche non persistono permanentemente sul disco o sono visibili ad altre transazioni fino a quando il database non riceve un COMMIT per la transazione corrente (che è ciò che session.commit()fa).

session.commit() esegue il commit (persiste) di tali modifiche al database.

flush()viene sempre chiamato come parte di una chiamata a commit()( 1 ).

Quando si utilizza un oggetto Session per eseguire una query sul database, la query restituirà risultati sia dal database che dalle parti svuotate della transazione non impegnata in suo possesso. Per impostazione predefinita, Session obietta le autoflushloro operazioni, ma questo può essere disabilitato.

Spero che questo esempio renderà questo più chiaro:

#---
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')>]

Ancora un'altra cosa: sai se chiamare commit () aumenta la memoria utilizzata o la diminuisce?
AP257,

2
Questo è anche falso per i motori db che non supportano transazioni come myisam. Non essendoci alcuna transazione in corso, flush ha ancora meno da distinguere dal commit.
underrun,

1
@underrun Quindi, se lo faccio session.query() dopo session.flush(), vedrò le mie modifiche? Dato che sto usando MyISAM.
Frozen Flame,

1
È uno stile buono o scarso da usare flush()e commit(), o dovrei lasciarlo a Alchemy. Ho usato flush()in alcuni casi perché le query successive dovevano raccogliere nuovi dati.
Jens,

1
@Jens Use autoflush( Trueper impostazione predefinita). Verrà scaricato automaticamente prima di tutte le query, quindi non è necessario ricordare ogni volta.
Kiran Jonnalagadda,

24

Come dice @snapshoe

flush() invia le tue istruzioni SQL al database

commit() esegue la transazione.

Quando session.autocommit == False:

commit()chiamerà flush()se si imposta autoflush == True.

Quando session.autocommit == True:

Non puoi chiamare commit()se non hai avviato una transazione (cosa che probabilmente non hai fatto poiché probabilmente utilizzeresti questa modalità solo per evitare la gestione manuale delle transazioni).

In questa modalità, è necessario chiamare flush()per salvare le modifiche ORM. Il flush commette efficacemente anche i tuoi dati.


24
"commit () chiamerà flush () se autoflush == True." non è del tutto corretto o è solo fuorviante. Commit flush sempre, indipendentemente dall'impostazione di autoflush.
Ilja Everilä,

3
Il autoflushparametro controlla se sqlalchemy prima emetterà un flush se ci sono scritture in sospeso prima di emettere una query e non ha nulla a che fare con il controllo dell'inevitabile flush su commit.
SuperShoot,

4

Perché scaricare se puoi impegnarti?

Essendo qualcuno che non ha mai lavorato con database e sqlalchemy, le risposte precedenti - che flush()inviano istruzioni SQL al DB e le commit()persistono - non mi erano chiare. Le definizioni hanno un senso, ma non è immediatamente chiaro dalle definizioni il motivo per cui dovresti usare un flush invece di impegnarti.

Poiché un commit arrossisce sempre ( https://docs.sqlalchemy.org/en/13/orm/session_basics.html#committing ) questi suoni sono molto simili. Penso che il grosso problema da evidenziare sia che un flush non è permanente e può essere annullato, mentre un commit è permanente, nel senso che non si può chiedere al database di annullare l'ultimo commit (penso)

@snapshoe evidenzia che se si desidera eseguire una query sul database e ottenere risultati che includono oggetti appena aggiunti, è necessario aver prima scaricato (o eseguito il commit, che scaricherà per voi). Forse questo è utile per alcune persone, anche se non sono sicuro del motivo per cui vorresti svuotare piuttosto che impegnarti (a parte la banale risposta che può essere annullata).

In un altro esempio stavo sincronizzando i documenti tra un DB locale e un server remoto e, se l'utente ha deciso di annullare, tutte le aggiunte / gli aggiornamenti / le eliminazioni dovrebbero essere annullate (ovvero nessuna sincronizzazione parziale, solo una sincronizzazione completa). Durante l'aggiornamento di un singolo documento ho deciso di eliminare semplicemente la vecchia riga e aggiungere la versione aggiornata dal server remoto. Si scopre che a causa del modo in cui viene scritta sqlalchemy, l'ordine delle operazioni durante il commit non è garantito. Ciò ha comportato l'aggiunta di una versione duplicata (prima di tentare di eliminare quella precedente), che ha comportato il fallimento di un vincolo univoco nel DB. Per flush()ovviare a questo ho usato in modo che l'ordine fosse mantenuto, ma potevo ancora annullare se in seguito il processo di sincronizzazione non fosse riuscito.

Vedi il mio post su questo su: Esiste un ordine per aggiungere o eliminare quando si commette in sqlalchemy

Allo stesso modo, qualcuno voleva sapere se viene mantenuto l'ordine di aggiunta durante il commit, ovvero se aggiungo object1quindi aggiungi object2, object1viene aggiunto al database prima che object2 SQLAlchemy salvi l'ordine quando si aggiungono oggetti alla sessione?

Ancora una volta, qui presumibilmente l'uso di un flush () garantirebbe il comportamento desiderato. Quindi, in sintesi, un uso di flush è quello di fornire garanzie di ordine (credo), pur concedendosi ancora un'opzione di "annullamento" che il commit non fornisce.

Autoflush e Autocommit

Nota, l'autoflush può essere utilizzato per garantire che le query agiscano su un database aggiornato poiché sqlalchemy scaricherà prima di eseguire la query. https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.params.autoflush

L'autocommit è qualcos'altro che non capisco completamente ma sembra che il suo uso sia scoraggiato: https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.params. autocommit

Utilizzo della memoria

Ora la domanda originale in realtà voleva sapere sull'impatto di flush vs. commit a fini di memoria. Dato che la capacità di persistere o meno è qualcosa che il database offre (penso), il semplice flushing dovrebbe essere sufficiente per scaricare nel database - anche se il commit non dovrebbe danneggiare (in realtà probabilmente aiuta - vedi sotto) se non ti interessa annullare .

sqlalchemy usa riferimenti deboli per oggetti che sono stati scaricati: https://docs.sqlalchemy.org/en/13/orm/session_state_management.html#session-referencing-behavior

Questo significa che se non hai un oggetto esplicitamente tenuto da qualche parte, come in un elenco o in un dict, sqlalchemy non lo manterrà in memoria.

Tuttavia, allora hai il lato database delle cose di cui preoccuparti. Presumibilmente il flushing senza impegno comporta una penalità di memoria per mantenere la transazione. Ancora una volta, sono nuovo a questo, ma ecco un link che sembra suggerire esattamente questo: https://stackoverflow.com/a/15305650/764365

In altre parole, i commit dovrebbero ridurre l'utilizzo della memoria, sebbene presumibilmente ci sia un compromesso tra memoria e prestazioni qui. In altre parole, probabilmente non si desidera eseguire il commit di ogni singola modifica del database, una alla volta (per motivi di prestazioni), ma attendere troppo a lungo aumenterà l'utilizzo della memoria.


1

Questo non risponde rigorosamente alla domanda originale, ma alcune persone hanno detto che con session.autoflush = Truete non devi usare session.flush()... E questo non è sempre vero.

Se si desidera utilizzare l'id di un oggetto appena creato nel mezzo di una transazione , è necessario chiamare 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

Questo è perché autoflushnon non auto riempire l'id (anche se una query dell'oggetto sarà, che a volte può causare confusione come in "perché questo funziona qui, ma non ci sono?" Ma snapshoe già coperto questa parte).


Un aspetto correlato che mi sembra abbastanza importante e che non è stato menzionato:

Perché non dovresti impegnarti tutto il tempo? - La risposta è atomicità .

Una parola di fantasia per dire: un insieme di operazioni devono tutte essere eseguito con successo o nessuno di essi avrà effetto.

Ad esempio, se si desidera creare / aggiornare / eliminare un oggetto (A) e quindi creare / aggiornare / eliminare un altro (B), ma se (B) non riesce, si desidera ripristinare (A). Ciò significa che quelle 2 operazioni sono atomiche .

Pertanto, se (B) necessita di un risultato di (A), si desidera chiamare flushdopo (A) e commitdopo (B).

Inoltre, se session.autoflush is True, ad eccezione del caso sopra citato o di altri nella risposta di Jimbo , non sarà necessario chiamare flushmanualmente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.