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

422

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

J'ai lu les documents, mais je ne suis pas plus sage - ils semblent supposer une pré-compréhension que je n'ai pas.

Je suis particulièrement intéressé par leur impact sur l'utilisation de la mémoire. Je charge 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 - c'est une grande base de données et une machine avec peu de mémoire.

Je me demande si j'utilise trop d' appels commit()et pas assez d' flush()appels - mais sans vraiment comprendre quelle est la différence, c'est difficile à dire!

AP257
la source

Réponses:

534

Un objet Session est essentiellement une transaction en cours de modifications apportées à une base de données (mise à jour, insertion, suppression). Ces opérations ne sont pas conservées dans la base de données jusqu'à ce qu'elles soient validées (si votre programme s'interrompt pour une raison quelconque lors d'une transaction en cours de session, toutes les modifications non validées à l'intérieur sont perdues).

L'objet session enregistre les opérations de transaction avec session.add(), mais ne les communique pas encore à la base de données jusqu'à ce qu'il session.flush()soit appelé.

session.flush()communique une série d'opérations à la base de données (insérer, mettre à jour, supprimer). La base de données les conserve en tant qu'opérations en attente dans une transaction. Les modifications ne sont pas conservées en permanence sur le disque ou visibles par les autres transactions jusqu'à ce que la base de données reçoive un COMMIT pour la transaction en cours (ce qui est le session.commit()cas).

session.commit() valide (persiste) ces modifications dans la base de données.

flush()est 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 à la fois de la base de données et des parties vidées de la transaction non validée qu'elle contient. Par défaut, Session s'oppose à autoflushleurs opérations, mais cela peut être désactivé.

Espérons que cet exemple rendra cela plus clair:

#---
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
la source
Encore une chose: savez-vous si l'appel de commit () augmente ou diminue la mémoire utilisée?
AP257
2
Cela est également faux pour les moteurs de base de données qui ne prennent pas en charge les transactions telles que myisam. Comme il n'y a pas de transaction en cours, flush a encore moins à se distinguer de commit.
underrun
1
@underrun Donc, si je le fais session.query() après session.flush(), verrai-je mes modifications? Étant donné que j'utilise MyISAM.
Frozen Flame du
1
Est-ce un style bon ou mauvais à utiliser flush()et commit(), ou devrais-je laisser cela à Alchemy. J'ai utilisé flush()dans certains cas parce que les requêtes suivantes devaient récupérer de nouvelles données.
Jens
1
@Jens Use autoflush( Truepar défaut). Il sera automatiquement vidé avant toutes les requêtes, vous n'avez donc pas besoin de vous en souvenir à chaque fois.
Kiran Jonnalagadda
24

Comme le dit @snapshoe

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

commit() valide la transaction.

Quand session.autocommit == False:

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

Quand session.autocommit == True:

Vous ne pouvez pas appeler commit()si vous n'avez pas commencé de transaction (ce que vous n'avez probablement pas car vous n'utiliseriez probablement ce mode que pour éviter de gérer manuellement les transactions).

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

Jacob
la source
24
"commit () appellera flush () si votre autoflush == True." n'est pas tout à fait correct, ou est simplement trompeur. Commit rince toujours, quel que soit le paramètre de rinçage automatique.
Ilja Everilä
3
Le autoflushparamètre contrôle si sqlalchemy émettra d'abord un vidage s'il y a des écritures en attente avant d'émettre une requête et n'a rien à voir avec le contrôle du vidage inévitable lors de la validation.
SuperShoot
4

Pourquoi vider si vous pouvez vous engager?

En tant que nouvel utilisateur de bases de données et de sqlalchemy, les réponses précédentes - qui flush()envoient des instructions SQL à la base de données et les conservent commit()- n'étaient pas claires pour moi. Les définitions ont un sens, mais il n'est pas immédiatement clair à partir des définitions pourquoi vous utiliseriez un vidage au lieu de simplement vous engager.

Étant donné qu'un commit vide toujours ( https://docs.sqlalchemy.org/en/13/orm/session_basics.html#committing ) ces sons sont vraiment similaires. Je pense que le gros problème à souligner est qu'un vidage 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 souhaitez interroger la base de données et obtenir des résultats qui incluent des objets nouvellement ajoutés, vous devez d'abord vider (ou valider, ce qui videra pour vous). Peut-être que cela est utile pour certaines personnes, même si je ne sais pas pourquoi vous souhaitez vider plutôt que de valider (à part la réponse triviale qu'elle peut être annulée).

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 devraient être annulés (c'est-à-dire pas de synchronisation partielle, seulement une synchronisation complète). Lors de la mise à jour d'un seul document, j'ai décidé de simplement supprimer l'ancienne ligne et d'ajouter la version mise à jour 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 entraîné l'échec d'une base de données unique de la base de données. Pour contourner cela, j'ai utilisé flush()afin que l'ordre soit maintenu, mais je pouvais toujours annuler si plus tard le processus de synchronisation échouait.

Voir mon article à ce sujet à: Y a - t-il une commande pour ajouter ou supprimer lors de la validation dans sqlalchemy

De même, quelqu'un voulait savoir si l'ajout d'un ordre est maintenu lors de la validation, c'est-à-dire si j'ajoute object1puis ajoute object2, est- object1il ajouté à la base de données avant object2 que SQLAlchemy enregistre l'ordre lors de l'ajout d'objets à la session?

Encore une fois, ici, vraisemblablement, l'utilisation d'un flush () assurerait le comportement souhaité. Donc, en résumé, une utilisation de flush est de fournir des garanties de commande (je pense), encore une fois tout en vous permettant une option "annuler" que la validation ne fournit pas.

Autoflush et Autocommit

Remarque, le nettoyage automatique peut être utilisé pour garantir que les requêtes agissent sur une base de données mise à jour car sqlalchemy sera vidé avant d'exécuter la requête. https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.params.autoflush

L'autocommit est 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. validation automatique

Utilisation de la mémoire

Maintenant, la question d'origine voulait en fait connaître l'impact de la purge par rapport à la validation à des fins de mémoire. Comme la capacité de persister ou non est quelque chose que la base de données offre (je pense), un simple rinçage devrait être suffisant pour décharger la base de données - bien que la validation ne devrait pas nuire (en fait probablement utile - voir ci-dessous) si vous ne vous souciez pas de l'annulation .

sqlalchemy utilise un 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 d'objet explicitement conservé quelque part, comme dans une liste ou un dict, sqlalchemy ne le gardera pas en mémoire.

Cependant, vous devez vous soucier du côté de la base de données. Vider probablement sans commettre s'accompagne d'une pénalité de mémoire pour maintenir la transaction. Encore une fois, je suis nouveau dans ce domaine, mais voici un lien qui semble suggérer exactement ceci: https://stackoverflow.com/a/15305650/764365

En d'autres termes, les validations devraient réduire l'utilisation de la mémoire, bien qu'il y ait vraisemblablement un compromis entre la mémoire et les performances ici. En d'autres termes, vous ne souhaiterez probablement pas valider chaque modification de la base de données une par une (pour des raisons de performances), mais une attente trop longue augmentera l'utilisation de la mémoire.

Jimbo
la source
1

Cela ne répond pas strictement à la question d'origine, mais certaines personnes ont mentionné que session.autoflush = Truevous n'avez pas à utiliser session.flush()... Et ce n'est pas toujours vrai.

Si vous souhaitez 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

C'est parce autoflushque NE remplit PAS automatiquement l'ID (bien qu'une requête de l'objet le fasse, ce qui peut parfois provoquer une confusion comme dans "pourquoi cela fonctionne ici mais pas là?" Mais snapshoe a déjà couvert cette partie).


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

Pourquoi ne vous engagez-vous pas tout le temps? - La réponse est l' atomicité .

Un mot de fantaisie à dire: un ensemble d'opérations doivent tous être exécutés avec succès ou aucun d'entre eux prendra effet.

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

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

De plus, si session.autoflush is True, à l'exception du cas que j'ai mentionné ci-dessus ou d'autres dans la réponse de Jimbo , vous n'aurez pas besoin d'appeler flushmanuellement.

Romain Vincent
la source