Existe alguma maneira de fazer com que o SQLAlchemy faça uma inserção em massa em vez de inserir cada objeto individual. ou seja,
fazendo:
INSERT INTO `foo` (`bar`) VALUES (1), (2), (3)
ao invés de:
INSERT INTO `foo` (`bar`) VALUES (1)
INSERT INTO `foo` (`bar`) VALUES (2)
INSERT INTO `foo` (`bar`) VALUES (3)
Acabei de converter um código para usar sqlalchemy em vez de raw sql e, embora agora seja muito mais agradável trabalhar com ele, parece mais lento agora (até um fator de 10), estou me perguntando se esse é o motivo.
Talvez eu possa melhorar a situação usando as sessões com mais eficiência. No momento eu tenho autoCommit=False
e faço um session.commit()
depois que eu adicionei algumas coisas. Embora isso pareça fazer com que os dados fiquem obsoletos se o banco de dados for alterado em outro lugar, como, mesmo que eu faça uma nova consulta, ainda recupero resultados antigos?
Obrigado pela ajuda!
Respostas:
SQLAlchemy introduziu isso na versão
1.0.0
:Operações em massa - SQLAlchemy docs
Com essas operações, agora você pode fazer inserções ou atualizações em massa!
Por exemplo, você pode fazer:
Aqui, uma inserção em massa será feita.
fonte
\copy
psql (do mesmo cliente para o mesmo servidor), vejo uma enorme diferença no desempenho no lado do servidor, resultando em cerca de 10x mais inserções / s. Aparentemente, o carregamento em massa é muito melhor usando\copy
(ouCOPY
no servidor) usando um pacote de comunicação entre cliente e servidor do que usar SQL via SQLAlchemy. Mais informações: grande volume de inserção diferença de desempenho PostgreSQL vs ... .Os documentos do sqlalchemy têm um resumo do desempenho de várias técnicas que podem ser usadas para inserções em massa:
fonte
Até onde eu sei, não há como fazer com que o ORM emita inserções em massa. Acredito que a razão subjacente é que o SQLAlchemy precisa acompanhar a identidade de cada objeto (ou seja, novas chaves primárias), e inserções em massa interferem nisso. Por exemplo, supondo que sua
foo
tabela contenha umaid
coluna e seja mapeada para umaFoo
classe:Como o SQLAlchemy selecionou o valor
x.id
sem emitir outra consulta, podemos inferir que ele obteve o valor diretamente daINSERT
instrução Se você não precisar de acesso subseqüente aos objetos criados pelas mesmas instâncias, poderá pular a camada ORM da sua inserção:O SQLAlchemy não pode corresponder essas novas linhas a nenhum objeto existente; portanto, você precisará consultá-las novamente para operações subsequentes.
No que diz respeito aos dados obsoletos, é útil lembrar que a sessão não possui uma maneira integrada de saber quando o banco de dados é alterado fora da sessão. Para acessar dados modificados externamente através de instâncias existentes, as instâncias devem ser marcadas como expiradas . Isso acontece por padrão
session.commit()
, mas pode ser feito manualmente chamandosession.expire_all()
ousession.expire(instance)
. Um exemplo (SQL omitido):session.commit()
expirax
, então a primeira instrução de impressão abre implicitamente uma nova transação e consulta novamente osx
atributos. Se você comentar a primeira declaração de impressão, notará que a segunda agora seleciona o valor correto, porque a nova consulta não é emitida até depois da atualização.Isso faz sentido do ponto de vista do isolamento transacional - você só deve captar modificações externas entre transações. Se isso estiver causando problemas, sugiro que você esclareça ou repense os limites de transação do aplicativo em vez de procurar imediatamente
session.expire_all()
.fonte
autocommit=False
, acredito que você deve ligarsession.commit()
após a conclusão da solicitação (não estou familiarizado com o TurboGears, então ignore isso se isso for tratado para você no nível da estrutura). Além de garantir que suas alterações tenham sido feitas no banco de dados, isso expiraria tudo na sessão. A próxima transação não começaria até o próximo uso dessa sessão, portanto, solicitações futuras no mesmo encadeamento não veriam dados obsoletos.session.execute(Foo.__table__.insert(), values)
Eu costumo fazer isso usando
add_all
.fonte
.add
à sessão, uma de cada vez?Add the given collection of instances to this Session.
você tem algum motivo para acreditar que ele não faz uma inserção em massa?.add
cada item individualmente.bulk_save_objects()
, com aflush()
, podemos obter o ID do objeto, masbulk_save_objects()
não podemos (evento comflush()
chamado).Suporte direto foi adicionado ao SQLAlchemy a partir da versão 0.8
De acordo com os documentos ,
connection.execute(table.insert().values(data))
deve fazer o truque. (Observe que isso não é o mesmoconnection.execute(table.insert(), data)
que resulta em muitas inserções de linha individuais por meio de uma chamada paraexecutemany
). Em qualquer coisa, exceto em uma conexão local, a diferença de desempenho pode ser enorme.fonte
SQLAlchemy introduziu isso na versão
1.0.0
:Operações em massa - SQLAlchemy docs
Com essas operações, agora você pode fazer inserções ou atualizações em massa!
Por exemplo (se você deseja a menor sobrecarga para INSERTs de tabela simples), pode usar
Session.bulk_insert_mappings()
:Ou, se desejar, pule as
loadme
tuplas e escreva os dicionários diretamentedicts
(mas acho mais fácil deixar toda a wordiness fora dos dados e carregar uma lista de dicionários em um loop).fonte
A resposta de Piere está correta, mas um problema é que,
bulk_save_objects
por padrão, não retorna as chaves primárias dos objetos, se isso lhe interessa. Definareturn_defaults
comoTrue
para obter esse comportamento.A documentação está aqui .
fonte
Todas as estradas levam a Roma , mas algumas atravessam montanhas, exigem balsas, mas se você quiser chegar rapidamente, pegue a rodovia.
Nesse caso, a auto-estrada deve usar o recurso execute_batch () do psycopg2 . A documentação diz o melhor:
A implementação atual de
executemany()
(usando um eufemismo extremamente caridoso) não está sendo executada particularmente. Essas funções podem ser usadas para acelerar a execução repetida de uma instrução contra um conjunto de parâmetros. Ao reduzir o número de viagens de ida e volta ao servidor, o desempenho pode ser uma ordem de magnitude melhor que o usoexecutemany()
.Em meu próprio teste
execute_batch()
é aproximadamente duas vezes mais rápido comoexecutemany()
, e dá a opção de configurar o page_size para mais ajustes (se você quiser espremer a última 2-3% do desempenho fora do motorista).O mesmo recurso pode ser facilmente ativado se você estiver usando SQLAlchemy, configurando
use_batch_mode=True
como um parâmetro ao instanciar o mecanismo comcreate_engine()
fonte
execute_values
é mais rápido que o psycopg2execute_batch
ao fazer inserções em massa!Esta é uma maneira:
Isso será inserido assim:
Referência: as perguntas frequentes do SQLAlchemy incluem referências para vários métodos de confirmação.
fonte
A melhor resposta que encontrei até agora foi na documentação do sqlalchemy:
http://docs.sqlalchemy.org/en/latest/faq/performance.html#im-inserting-400-000-rows-with-the-orm-and-it-s-really-slow
Há um exemplo completo de uma referência de possíveis soluções.
Como mostrado na documentação:
bulk_save_objects não é a melhor solução, mas seu desempenho está correto.
A segunda melhor implementação em termos de legibilidade, acho que foi com o SQLAlchemy Core:
O contexto dessa função é fornecido no artigo de documentação.
fonte