SQLAlchemy: ¿Cuál es la diferencia entre flush () y commit ()?

422

¿Cuál es la diferencia entre flush()y commit()en SQLAlchemy?

He leído los documentos, pero no soy el más sabio, parecen asumir un pre-entendimiento que no tengo.

Estoy particularmente interesado en su impacto en el uso de la memoria. Estoy cargando algunos datos en una base de datos de una serie de archivos (alrededor de 5 millones de filas en total) y mi sesión ocasionalmente se está cayendo: es una gran base de datos y una máquina con poca memoria.

Me pregunto si estoy usando demasiadas commit()y no hay suficientes flush()llamadas, pero sin comprender realmente cuál es la diferencia, ¡es difícil saberlo!

AP257
fuente

Respuestas:

534

Un objeto de sesión es básicamente una transacción continua de cambios en una base de datos (actualizar, insertar, eliminar). Estas operaciones no se guardan en la base de datos hasta que se confirman (si su programa cancela por alguna razón en la transacción de mitad de sesión, se pierden los cambios no confirmados).

El objeto de sesión registra las operaciones de transacción session.add(), pero aún no las comunica a la base de datos hasta que session.flush()se invoca.

session.flush()comunica una serie de operaciones a la base de datos (insertar, actualizar, eliminar). La base de datos los mantiene como operaciones pendientes en una transacción. Los cambios no se conservan permanentemente en el disco ni son visibles para otras transacciones hasta que la base de datos recibe un COMPROMISO para la transacción actual (que es lo que session.commit()hace).

session.commit() confirma (persiste) esos cambios en la base de datos.

flush()está siempre llamado como parte de una llamada a commit()( 1 ).

Cuando utiliza un objeto Session para consultar la base de datos, la consulta devolverá resultados tanto de la base de datos como de las partes enjuagadas de la transacción no confirmada que contiene. De forma predeterminada, la Sesión objeta autoflushsus operaciones, pero esto puede deshabilitarse.

Esperemos que este ejemplo aclare esto:

#---
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')>]
zapata
fuente
Solo una cosa más: ¿sabe si llamar a commit () aumenta la memoria utilizada o la disminuye?
AP257
2
Esto también es falso para los motores db que no admiten transacciones como myisam. Como no hay una transacción en curso, flush tiene aún menos que distinguirse de commit.
Underrun
1
@underrun Entonces, si lo hago session.query() después session.flush(), ¿veré mis cambios? Dado que estoy usando MyISAM.
Frozen Flame
1
¿Es un estilo bueno o malo de usar flush()y commit(), o debería dejarlo en manos de Alchemy? Utilicé flush()en algunos casos porque las consultas posteriores necesitaban recoger nuevos datos.
Jens
1
@Jens Use autoflush( Truepor defecto). Se enjuagará automáticamente antes de todas las consultas, por lo que no tiene que recordar cada vez.
Kiran Jonnalagadda
24

Como @snapshoe dice

flush() envía sus declaraciones SQL a la base de datos

commit() confirma la transacción

Cuando session.autocommit == False:

commit()llamará flush()si configura autoflush == True.

Cuando session.autocommit == True:

No puede llamar commit()si no ha iniciado una transacción (lo cual probablemente no haya hecho, ya que probablemente solo usaría este modo para evitar la administración manual de transacciones).

En este modo, debe llamar flush()para guardar sus cambios de ORM. El vaciado también confirma sus datos de manera efectiva.

Jacob
fuente
24
"commit () llamará a flush () si su autoflush == True". no es del todo correcto o simplemente es engañoso. Confirmar siempre los enjuagues, independientemente de la configuración de enjuague automático.
Ilja Everilä
3
El autoflushparámetro controla si sqlalchemy emitirá primero una descarga si hay escrituras pendientes antes de emitir una consulta y no tiene nada que ver con el control de la descarga inevitable al confirmar.
SuperShoot
4

¿Por qué enjuagarse si puedes comprometerte?

Como alguien nuevo en trabajar con bases de datos y sqlalchemy, las respuestas anteriores, que flush()envían declaraciones SQL a la base de datos y las commit()persisten, no me quedaron claras. Las definiciones tienen sentido, pero las definiciones no aclaran de inmediato por qué usaría una descarga en lugar de solo comprometerse.

Dado que una confirmación siempre se vacía ( https://docs.sqlalchemy.org/en/13/orm/session_basics.html#committing ), esto suena muy similar. Creo que el gran problema a destacar es que una descarga no es permanente y se puede deshacer, mientras que un commit es permanente, en el sentido de que no se puede pedir a la base de datos que deshaga el último commit (creo)

@snapshoe destaca que si desea consultar la base de datos y obtener resultados que incluyan objetos recién agregados, primero debe enjuagarse (o confirmarse, lo que enjuagará por usted). Tal vez esto sea útil para algunas personas, aunque no estoy seguro de por qué querría enjuagarse en lugar de comprometerse (aparte de la respuesta trivial de que se puede deshacer).

En otro ejemplo, estaba sincronizando documentos entre una base de datos local y un servidor remoto, y si el usuario decidía cancelar, todas las adiciones / actualizaciones / eliminaciones deberían deshacerse (es decir, sin sincronización parcial, solo una sincronización completa). Al actualizar un solo documento, he decidido simplemente eliminar la fila anterior y agregar la versión actualizada del servidor remoto. Resulta que, debido a la forma en que se escribe sqlalchemy, el orden de las operaciones cuando se confirma no está garantizado. Esto resultó en la adición de una versión duplicada (antes de intentar eliminar la anterior), lo que provocó que el DB fallara una restricción única. Para solucionar esto, utilicé flush()para mantener el orden, pero aún podía deshacerlo si luego el proceso de sincronización fallaba.

Vea mi publicación sobre esto en: ¿Hay algún orden para agregar versus eliminar cuando se confirma en sqlalchemy

Del mismo modo, alguien quería saber si el orden de agregar se mantiene cuando se confirma, es decir, si agrego y object1luego agrego object2, ¿ object1se agrega a la base de datos antes? object2 ¿SQLAlchemy guarda el orden al agregar objetos a la sesión?

De nuevo, aquí presumiblemente el uso de flush () aseguraría el comportamiento deseado. En resumen, un uso para flush es proporcionar garantías de orden (creo), de nuevo mientras se permite una opción de "deshacer" que commit no proporciona.

Autoflush y Autocommit

Tenga en cuenta que el enjuague automático se puede usar para garantizar que las consultas actúen en una base de datos actualizada, ya que sqlalchemy se enjuagará antes de ejecutar la consulta. https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.params.autoflush

La confirmación automática es algo más que no entiendo completamente, pero parece que se desaconseja su uso: https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.params. confirmación automática

Uso de memoria

Ahora la pregunta original realmente quería saber sobre el impacto de flush vs. commit para propósitos de memoria. Como la capacidad de persistir o no es algo que ofrece la base de datos (creo), simplemente el enjuague debería ser suficiente para descargar en la base de datos, aunque el compromiso no debería dañar (en realidad probablemente ayude, ver a continuación) si no le importa deshacer .

sqlalchemy utiliza referencias débiles para objetos que se han vaciado: https://docs.sqlalchemy.org/en/13/orm/session_state_management.html#session-referencing-behavior

Esto significa que si no tiene un objeto retenido explícitamente en algún lugar, como en una lista o dict, sqlalchemy no lo guardará en la memoria.

Sin embargo, entonces tiene que preocuparse por el lado de la base de datos. Presumiblemente, el vaciado sin comprometer viene con una penalización de memoria para mantener la transacción. Nuevamente, soy nuevo en esto, pero aquí hay un enlace que parece sugerir exactamente esto: https://stackoverflow.com/a/15305650/764365

En otras palabras, los commits deberían reducir el uso de memoria, aunque presumiblemente hay una compensación entre memoria y rendimiento aquí. En otras palabras, probablemente no desee confirmar cada cambio de base de datos, uno a la vez (por razones de rendimiento), pero esperar demasiado aumentará el uso de memoria.

Jimbo
fuente
1

Esto no responde estrictamente a la pregunta original, pero algunas personas han mencionado que con session.autoflush = Trueusted no tiene que usar session.flush()... Y esto no siempre es cierto.

Si desea utilizar la identificación de un objeto recién creado en medio de una transacción , debe llamar 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

Esto se debe a autoflushque NO llena automáticamente la identificación (aunque una consulta del objeto sí lo hará, lo que a veces puede causar confusión como en "¿por qué esto funciona aquí pero no allí?", Pero Snapshoe ya cubrió esta parte).


Un aspecto relacionado que me parece bastante importante y que en realidad no fue mencionado:

¿Por qué no te comprometes todo el tiempo? - La respuesta es atomicidad .

Una palabra elegante para decir: un conjunto de operaciones tiene que todo ser ejecutado con éxito o ninguno de ellos tenga efecto.

Por ejemplo, si desea crear / actualizar / eliminar algún objeto (A) y luego crear / actualizar / eliminar otro (B), pero si (B) falla, desea revertir (A). Esto significa que esas 2 operaciones son atómicas .

Por lo tanto, si (B) necesita un resultado de (A), desea llamar flushdespués (A) y commitdespués (B).

Además, si session.autoflush is True, a excepción del caso que mencioné anteriormente u otros en la respuesta de Jimbo , no necesitará llamar flushmanualmente.

Romain Vincent
fuente