SQLAlchemy: Qual é a diferença entre flush () e commit ()?

422

Qual a diferença entre flush()e commit()no SQLAlchemy?

Eu li os documentos, mas não sou o mais sábio - eles parecem assumir um pré-entendimento que eu não tenho.

Estou particularmente interessado em seu impacto no uso da memória. Estou carregando alguns dados em um banco de dados a partir de uma série de arquivos (cerca de 5 milhões de linhas no total) e minha sessão está caindo ocasionalmente - é um banco de dados grande e uma máquina com pouca memória.

Gostaria de saber se estou usando chamadas demais commit()e não suficientes flush()- mas sem realmente entender qual é a diferença, é difícil dizer!

AP257
fonte

Respostas:

534

Um objeto Session é basicamente uma transação contínua de alterações em um banco de dados (atualização, inserção, exclusão). Essas operações não são mantidas no banco de dados até serem confirmadas (se o seu programa for interrompido por algum motivo na transação no meio da sessão, quaisquer alterações não confirmadas serão perdidas).

O objeto de sessão registra operações de transação com session.add(), mas ainda não as comunica com o banco de dados até que session.flush()seja chamado.

session.flush()comunica uma série de operações ao banco de dados (inserir, atualizar, excluir). O banco de dados os mantém como operações pendentes em uma transação. As alterações não são mantidas permanentemente no disco ou visíveis para outras transações até que o banco de dados receba um COMMIT para a transação atual (que é o que recebe session.commit()).

session.commit() confirma (persiste) essas alterações no banco de dados.

flush()é sempre chamado como parte de uma chamada para commit()( 1 ).

Quando você usa um objeto Session para consultar o banco de dados, a consulta retornará resultados do banco de dados e das partes liberadas da transação não confirmada que ele mantém. Por padrão, a Session objetiva autoflushsuas operações, mas isso pode ser desativado.

Espero que este exemplo torne isso mais claro:

#---
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
fonte
Só mais uma coisa: você sabe se chamar commit () aumenta a memória usada ou diminui?
AP257 17/11/2010
2
Isso também é falso para mecanismos de banco de dados que não suportam transações como o myisam. Como não há transação em andamento, o flush tem ainda menos a distinguir-se do commit.
underrun
1
@underrun Então, se eu fizer session.query() depois session.flush(), verei minhas alterações? Dado que estou usando o MyISAM.
Frozen Flame
1
É um estilo bom ou ruim de usar flush()e commit(), ou devo deixar isso para a Alquimia. Eu usei flush()em alguns casos porque as consultas subseqüentes precisavam coletar novos dados.
Jens
1
@Jens Use autoflush( Truepor padrão). Ele será liberado automaticamente antes de todas as consultas, para que você não precise se lembrar sempre.
precisa
24

Como @snapshoe diz

flush() envia suas instruções SQL para o banco de dados

commit() confirma a transação.

Quando session.autocommit == False:

commit()irá ligar flush()se você definir autoflush == True.

Quando session.autocommit == True:

Você não pode ligar commit()se não tiver iniciado uma transação (o que provavelmente não iniciou, pois provavelmente usaria esse modo apenas para evitar o gerenciamento manual de transações).

Nesse modo, você deve ligar flush()para salvar suas alterações no ORM. O flush efetivamente também confirma seus dados.

Jacob
fonte
24
"commit () chamará flush () se o seu autoflush == True." não é totalmente correto ou é apenas enganoso. A confirmação sempre libera, independentemente da configuração de autoflush.
Ilja Everilä 27/10/19
3
O autoflushparâmetro controla se o sqlalchemy emitirá um flush primeiro se houver gravações pendentes antes de emitir uma consulta e não tiver nada a ver com o controle do inevitável flush no commit.
SuperShoot
4

Por que liberar se você pode confirmar?

Como alguém novo no trabalho com bancos de dados e sqlalchemy, as respostas anteriores - que flush()enviam instruções SQL ao banco de dados e as commit()persistem - não estavam claras para mim. As definições fazem sentido, mas não fica claro imediatamente a partir das definições por que você usaria um flush em vez de apenas confirmar.

Como um commit sempre libera ( https://docs.sqlalchemy.org/en/13/orm/session_basics.html#committing ), esses sons são realmente semelhantes. Eu acho que o grande problema a destacar é que um flush não é permanente e pode ser desfeito, enquanto um commit é permanente, no sentido de que você não pode pedir ao banco de dados para desfazer o último commit (eu acho)

O @snapshoe destaca que, se você deseja consultar o banco de dados e obter resultados que incluam objetos recém-adicionados, você precisa primeiro liberar (ou confirmar, o que liberará para você). Talvez isso seja útil para algumas pessoas, embora eu não saiba por que você desejaria liberar em vez de confirmar (além da resposta trivial de que pode ser desfeita).

Em outro exemplo, eu estava sincronizando documentos entre um banco de dados local e um servidor remoto e, se o usuário decidisse cancelar, todas as adições / atualizações / exclusões deveriam ser desfeitas (ou seja, sem sincronização parcial, apenas uma sincronização completa). Ao atualizar um único documento, decidi simplesmente excluir a linha antiga e adicionar a versão atualizada do servidor remoto. Acontece que, devido à maneira como o sqlalchemy é gravado, a ordem das operações ao confirmar não é garantida. Isso resultou na adição de uma versão duplicada (antes de tentar excluir a antiga), o que resultou na falha do banco de dados em uma restrição exclusiva. Para contornar isso, usei flush()para que a ordem fosse mantida, mas ainda assim eu poderia desfazer se o processo de sincronização falhar mais tarde.

Veja meu post sobre isso em: Existe alguma ordem para adicionar versus excluir ao confirmar em sqlalchemy

Da mesma forma, alguém queria saber se a ordem de adição é mantida ao confirmar, ou seja, se eu adicionar e object1depois adicionar object2, ela será object1adicionada ao banco de dados antes do object2 SQLAlchemy salvar a ordem ao adicionar objetos à sessão?

Novamente, aqui presumivelmente o uso de um flush () garantiria o comportamento desejado. Portanto, em resumo, um uso para o flush é fornecer garantias de pedidos (eu acho), novamente enquanto ainda se permite uma opção "desfazer" que o commit não fornece.

Autoflush e Autocommit

Observe que o autoflush pode ser usado para garantir que as consultas atuem em um banco de dados atualizado, pois o sqlalchemy será liberado antes de executar a consulta. https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.params.autoflush

A confirmação automática é outra coisa que eu não entendo completamente, mas parece que seu uso é desencorajado: https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.params. confirmação automática

Uso de memória

Agora, a pergunta original realmente queria saber sobre o impacto do flush vs. commit para fins de memória. Como a capacidade de persistir ou não é algo que o banco de dados oferece (eu acho), a simples descarga deve ser suficiente para ser transferida para o banco de dados - embora a confirmação não seja prejudicial (na verdade, provavelmente ajuda - veja abaixo) se você não se importa em desfazer .

O sqlalchemy usa referências fracas para objetos que foram liberados: https://docs.sqlalchemy.org/en/13/orm/session_state_management.html#session-referencing-behavior

Isso significa que se você não tiver um objeto explicitamente mantido em algum lugar, como em uma lista ou ditado, o sqlalchemy não o manterá na memória.

No entanto, você precisa se preocupar com o lado do banco de dados. Presumivelmente, a liberação sem confirmar vem com alguma penalidade de memória para manter a transação. Novamente, sou novo nisso, mas aqui está um link que parece sugerir exatamente isso: https://stackoverflow.com/a/15305650/764365

Em outras palavras, as confirmações devem reduzir o uso de memória, embora presumivelmente haja uma troca entre memória e desempenho aqui. Em outras palavras, você provavelmente não deseja confirmar todas as alterações no banco de dados, uma de cada vez (por motivos de desempenho), mas esperar muito tempo aumentará o uso de memória.

Jimbo
fonte
1

Isso não responde estritamente à pergunta original, mas algumas pessoas mencionaram que session.autoflush = Truevocê não precisa usar session.flush()... E isso nem sempre é verdade.

Se você deseja usar o ID de um objeto recém-criado no meio de uma transação , deve chamar 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

Isso ocorre porque autoflushnão NÃO auto preencher o id (embora uma consulta do objeto vai, o que por vezes pode causar confusão como em "por que isso funciona aqui, mas não há?" Mas snapshoe já cobertas esta parte).


Um aspecto relacionado que parece muito importante para mim e não foi realmente mencionado:

Por que você não cometeu o tempo todo? - A resposta é atomicidade .

A palavra extravagante para dizer: um conjunto de operações que tudo seja executado com sucesso OU nenhum deles terá efeito.

Por exemplo, se você deseja criar / atualizar / excluir algum objeto (A) e depois criar / atualizar / excluir outro (B), mas se (B) falhar, você deseja reverter (A). Isso significa que essas duas operações são atômicas .

Portanto, se (B) precisar de um resultado de (A), você deseja chamar flushdepois de (A) e commitdepois de (B).

Além disso, se session.autoflush is True, exceto no caso que mencionei acima ou outros na resposta de Jimbo , você não precisar ligar flushmanualmente.

Romain Vincent
fonte