597 votes

SQLAlchemy : Quelle est la différence entre flush() et commit() ?

Quelle est la différence entre flush() y commit() dans SQLAlchemy ?

J'ai lu les documents, mais je n'en sais rien - ils semblent supposer une compréhension préalable que je n'ai pas.

Je suis particulièrement intéressé par leur impact sur l'utilisation de la mémoire. Je suis en train de charger des données dans une base de données à partir d'une série de fichiers (environ 5 millions de lignes au total) et ma session tombe parfois en panne - c'est une grande base de données et une machine avec peu de mémoire.

Je me demande si je n'en utilise pas trop. commit() et pas assez flush() mais sans vraiment comprendre quelle est la différence, c'est difficile à dire !

767voto

snapshoe Points 3455

Un objet Session est essentiellement une transaction en cours de modification d'une base de données (mise à jour, insertion, suppression). Ces opérations ne sont pas persistées dans la base de données tant qu'elles ne sont pas validées (si votre programme s'interrompt pour une raison quelconque au milieu d'une transaction de session, toutes les modifications non validées sont perdues).

L'objet de session enregistre les opérations de transaction avec session.add() mais ne les communique pas encore à la base de données jusqu'à ce que session.flush() s'appelle.

session.flush() communique une série d'opérations à la base de données (insertion, mise à jour, suppression). La base de données les maintient en tant qu'opérations en attente dans une transaction. Les modifications ne sont pas persistées de façon permanente sur le disque, ni visibles pour les autres transactions, jusqu'à ce que la base de données reçoive un COMMIT pour la transaction en cours (ce qui est ce que l'on appelle le "COMMIT"). session.commit() fait).

session.commit() commet (persiste) ces changements dans la base de données.

flush() es toujours appelé dans le cadre d'un appel à commit() ( 1 ).

Lorsque vous utilisez un objet Session pour interroger la base de données, la requête renvoie des résultats provenant à la fois de la base de données et des parties vidées de la transaction non validée qu'il contient. Par défaut, les objets Session autoflush leurs opérations, mais cela peut être désactivé.

Nous espérons que cet exemple rendra les choses plus claires :

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

0 votes

Encore une chose : savez-vous si l'appel à commit() augmente la mémoire utilisée, ou la diminue ?

3 votes

Ceci est également faux pour les moteurs de bases de données qui ne supportent pas les transactions, comme myisam. Comme il n'y a pas de transaction en cours, flush se distingue encore moins de commit.

1 votes

@underrun Alors si je fais session.query() après session.flush() Est-ce que je verrai mes modifications ? Étant donné que j'utilise MyISAM.

41voto

Romain Vincent Points 819

Cela ne répond pas strictement à la question originale mais certaines personnes ont mentionné qu'avec session.autoflush = True vous n'avez pas besoin d'utiliser session.flush() ... Et ce n'est pas toujours vrai.

Si vous voulez utiliser l'id d'un objet nouvellement créé au milieu d'une transaction vous devez appeler 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

Cela s'explique par le fait que autoflush fait PAS auto remplit l'id (bien qu'une requête de l'objet le fera, ce qui peut parfois causer de la confusion comme dans "pourquoi cela fonctionne ici mais pas là ?". Mais mousqueton a déjà couvert cette partie).


Un aspect connexe qui me semble assez important et qui n'a pas vraiment été mentionné :

Pourquoi ne pas s'engager tout le temps ? - La réponse est atomicité .

Un mot savant pour dire : un ensemble d'opérations doivent tous être exécutés avec succès OU aucun d'entre eux n'aura d'effet.

Par exemple, si vous voulez créer/mettre à jour/supprimer un objet (A) et ensuite créer/mettre à jour/supprimer un autre (B), mais si (B) échoue, vous voulez revenir à (A). Cela signifie que ces 2 opérations sont atomique .

Par conséquent, si (B) a besoin d'un résultat de (A), vous voulez appeler flush après (A) et commit après (B).

En outre, si session.autoflush is True sauf pour le cas que j'ai mentionné plus haut ou d'autres en Jimbo vous n'aurez pas besoin d'appeler flush manuellement.

33voto

Jacob Points 1049

Comme le dit @snapshoe

flush() envoie vos instructions SQL à la base de données

commit() engage la transaction.

Lorsque session.autocommit == False :

commit() appellera flush() si vous définissez autoflush == True .

Lorsque session.autocommit == True :

Vous ne pouvez pas appeler commit() si vous n'avez pas lancé de transaction (ce qui n'est probablement pas le cas puisque vous n'utilisez ce mode que pour éviter de gérer manuellement les transactions).

Dans ce mode, vous devez appeler flush() pour enregistrer vos modifications ORM. Le flush permet également de valider vos données.

27voto

Adam Hughes Points 4177

Utilisez flush lorsque vous devez simuler une écriture, par exemple pour obtenir un ID de clé primaire à partir d'un compteur auto-incrémenté.

john=Person(name='John Smith', parent=None)
session.add(john)
session.flush()

son=Person(name='Bill Smith', parent=john.id)

Sans tirer la chasse, john n'obtiendrait jamais d'ID de la base de données et ne pourrait donc pas représenter la relation parent/enfant dans le code.

Comme d'autres l'ont dit, sans commit() rien de tout cela ne sera conservé de façon permanente dans la base de données.

25voto

Jimbo Points 789

Pourquoi tirer la chasse si vous pouvez vous engager ?

En tant que novice dans le domaine des bases de données et de sqlalchemy, je trouve les réponses précédentes - que flush() envoie des instructions SQL à la base de données et commit() les persiste - n'étaient pas claires pour moi. Les définitions ont un sens, mais il n'est pas immédiatement clair, à partir de ces définitions, pourquoi vous utilisez un flush au lieu de simplement commiter.

Étant donné qu'un commit vide toujours la mémoire ( https://docs.sqlalchemy.org/en/13/orm/session_basics.html#committing ), elles se ressemblent beaucoup. Je pense que le gros problème à souligner est qu'un flush n'est pas permanent et peut être annulé, alors qu'un commit est permanent, dans le sens où vous ne pouvez pas demander à la base de données d'annuler le dernier commit (je pense).

@snapshoe souligne que si vous voulez interroger la base de données et obtenir des résultats qui incluent les objets nouvellement ajoutés, vous devez d'abord effectuer un flush (ou un commit, qui effectuera le flush pour vous). Cela peut être utile pour certaines personnes, bien que je ne sois pas sûr de la raison pour laquelle vous voudriez faire un flush plutôt qu'un commit (à part la réponse triviale que cela peut être annulé).

Dans un autre exemple, je synchronisais des documents entre une base de données locale et un serveur distant, et si l'utilisateur décidait d'annuler, tous les ajouts/mises à jour/suppressions devaient être annulés (c'est-à-dire qu'il n'y avait pas de synchronisation partielle, seulement une synchronisation complète). Lors de la mise à jour d'un seul document, j'ai décidé de supprimer simplement l'ancienne ligne et d'ajouter la version mise à jour à partir du serveur distant. Il s'avère qu'en raison de la façon dont sqlalchemy est écrit, l'ordre des opérations lors de la validation n'est pas garanti. Cela a entraîné l'ajout d'une version en double (avant de tenter de supprimer l'ancienne), ce qui a fait échouer une contrainte unique dans la base de données. Pour contourner ce problème, j'ai utilisé flush() afin que l'ordre soit maintenu, mais je pouvais toujours annuler si le processus de synchronisation échouait plus tard.

Voir mon billet à ce sujet à l'adresse suivante Y a-t-il un ordre pour l'ajout ou la suppression lors de la validation dans sqlalchemy ?

De même, quelqu'un voulait savoir si l'ordre d'ajout est maintenu lors de la validation, c'est-à-dire que si j'ajoute object1 puis ajoutez object2 , fait object1 sont ajoutés à la base de données avant object2 Est-ce que SQLAlchemy sauvegarde l'ordre lors de l'ajout d'objets à la session ?

Là encore, l'utilisation d'un flush() permettrait d'obtenir le comportement souhaité. Donc, en résumé, une utilisation de flush est de fournir des garanties d'ordre (je pense), encore une fois tout en vous permettant une option "undo" que commit ne fournit pas.

Autoflush et Autocommit

Notez que l'autoflush peut être utilisé pour s'assurer que les requêtes agissent sur une base de données mise à jour, car sqlalchemy fera un flush avant d'exécuter la requête. https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.params.autoflush

Autocommit est une autre chose que je ne comprends pas complètement, mais il semble que son utilisation soit déconseillée : https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.params.autocommit

Utilisation de la mémoire

La question originale voulait en fait connaître l'impact du flush par rapport au commit pour les besoins de la mémoire. Comme la possibilité de persister ou non est quelque chose que la base de données offre (je pense), un simple flush devrait être suffisant pour décharger vers la base de données - bien que le commit ne devrait pas faire de mal (en fait, il aide probablement - voir ci-dessous) si vous ne vous souciez pas d'annuler.

sqlalchemy utilise le référencement faible pour les objets qui ont été vidés : https://docs.sqlalchemy.org/en/13/orm/session_state_management.html#session-referencing-behavior

Cela signifie que si vous n'avez pas un objet explicitement maintenu quelque part, comme dans une liste ou un dict, sqlalchemy ne le gardera pas en mémoire.

Cependant, il faut alors se préoccuper de l'aspect base de données. On peut supposer que le fait de vider les données sans les valider entraîne une pénalité mémoire pour maintenir la transaction. Encore une fois, je suis novice en la matière, mais voici un lien qui semble suggérer exactement cela : https://stackoverflow.com/a/15305650/764365

En d'autres termes, les commits devraient réduire l'utilisation de la mémoire, bien que l'on puisse supposer qu'il existe un compromis entre la mémoire et les performances. En d'autres termes, vous ne souhaitez probablement pas valider chaque modification de la base de données, une par une (pour des raisons de performances), mais attendre trop longtemps augmentera l'utilisation de la mémoire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X