Pandas: to_sql é muito lento

Criado em 1 fev. 2017  ·  24Comentários  ·  Fonte: pandas-dev/pandas

Amostra de código,

df_name.to_sql('table_name',
                          schema = 'public',
                          con = engine,
                          index = False,
                          if_exists = 'replace')

Descrição do Problema

Estou escrevendo um dataframe de 500.000 linhas para um banco de dados AWS do postgres e leva muito, muito tempo para enviar os dados.

É um servidor SQL bastante grande e minha conexão com a Internet é excelente, portanto, descartei aqueles que contribuem para o problema.

Em comparação, csv2sql ou usar cat e piping no psql na linha de comando é muito mais rápido.

IO SQL Usage Question

Comentários muito úteis

Adicione este código abaixo de engine = create_engine(connection_string) :

from sqlalchemy import event

@event.listens_for(e, 'before_cursor_execute')
def receive_before_cursor_execute(conn, cursor, statement, params, context, executemany):
    if executemany:
        cursor.fast_executemany = True
        cursor.commit()

No meu código, a função to_sql levava 7 minutos para ser executada e agora leva apenas 5 segundos;)

Todos 24 comentários

veja aqui: http://stackoverflow.com/questions/33816918/write-large-pandas-dataframes-to-sql-server-database

com SQLServer você precisa importar via csv com um upload em massa para eficiência

você pode achar isso útil: http://odo.pydata.org/en/latest/perf.html

ODO não funcionaria para mim, ele gera erros que não fui capaz de corrigir, mas d6tstack funcionou bem https://github.com/d6t/d6tstack/blob/master/examples-sql.ipynb. Você pode pré-processar com o pandas e ele usa o postgres COPY FROM para tornar a importação rápida. Funciona bem com postgres RDS.

Adicione este código abaixo de engine = create_engine(connection_string) :

from sqlalchemy import event

@event.listens_for(e, 'before_cursor_execute')
def receive_before_cursor_execute(conn, cursor, statement, params, context, executemany):
    if executemany:
        cursor.fast_executemany = True
        cursor.commit()

No meu código, a função to_sql levava 7 minutos para ser executada e agora leva apenas 5 segundos;)

Obrigado @llautert!
Isso ajudou muito!

# dont forget to import event
from sqlalchemy import event, create_engine

engine = create_engine(connection_string)

@event.listens_for(engine, 'before_cursor_execute')
def receive_before_cursor_execute(conn, cursor, statement, params, context, executemany):
    if executemany:
        cursor.fast_executemany = True
        cursor.commit()

Tentei executar essa correção, mas recebi uma mensagem de erro:

AttributeError: 'psycopg2.extensions.cursor' object has no attribute 'fast_executemany'

Alguém sabe o que está acontecendo?

Ei @ tim-sauchuk, encontrando o mesmo erro também, embora eu tenha encontrado uma solução que está funcionando muito bem que envolve uma pequena edição no arquivo pandas.io.sql.py (apenas exclua o arquivo .pyc de __pycache__ antes de importar novamente para certificar-se de que grava a nova versão no arquivo compactado)

https://github.com/pandas-dev/pandas/issues/8953

Ei @ tim-sauchuk, encontrando o mesmo erro também, embora eu tenha encontrado uma solução que está funcionando muito bem que envolve uma pequena edição no arquivo pandas.io.sql.py (apenas exclua o arquivo .pyc do

8953

O problema # 8953 que @ bsaunders23 mencionou também mostra uma maneira de "corrigir o patch" (corrigi-lo em tempo de execução). Eu tentei, e um conjunto de dados de 20k que levou mais de 10 minutos para carregar, em seguida, levou apenas 4 segundos.

Obrigado @llautert!
Isso ajudou muito!

# dont forget to import event
from sqlalchemy import event, create_engine

engine = create_engine(connection_string)

@event.listens_for(engine, 'before_cursor_execute')
def receive_before_cursor_execute(conn, cursor, statement, params, context, executemany):
    if executemany:
        cursor.fast_executemany = True
        cursor.commit()

Alguém sabe como posso implementar essa solução dentro de uma classe com uma instância self.engine?

Alguém sabe como posso implementar essa solução dentro de uma classe com uma instância self.engine?

Funciona para mim referindo-se a self.engine

Exemplo:

    self.engine = sqlalchemy.create_engine(connectionString, echo=echo)
    self.connection = self.engine.connect()

    @event.listens_for(self.engine, 'before_cursor_execute')
    def receive_before_cursor_execute(conn, cursor, statement, params, context, executemany):
        print("Listen before_cursor_execute - executemany: %s" % str(executemany))
        if executemany:
            cursor.fast_executemany = True
            cursor.commit()

Nao funciona para mim. Qual versão de pandas e sqlalchemy você está usando?

Eu tentei executando sqlalchemy: 1.2.4-py35h14c3975_0 e 1.2.11-py35h7b6447c_0

mas estou conseguindo

AttributeError: o objeto 'psycopg2.extensions.cursor' não tem o atributo 'fast_executemany'

@ dean12 @llautert

Qual é a aparência da chamada de função neste contexto? Ou em outras palavras, o que você está usando como argumentos para fazer o upload da tabela com sucesso?

<# dont forget to import event
from sqlalchemy import event, create_engine

engine = create_engine(connection_string)

@event.listens_for(engine, 'before_cursor_execute')
def receive_before_cursor_execute(conn, cursor, statement, params, context, executemany):
    if executemany:
        cursor.fast_executemany = True
        cursor.commit()>``

Eu tentei executando sqlalchemy: 1.2.4-py35h14c3975_0 e 1.2.11-py35h7b6447c_0

mas estou conseguindo

AttributeError: o objeto 'psycopg2.extensions.cursor' não tem o atributo 'fast_executemany'

Você está usando psycopg2, que é um driver postgresql. Esse problema e a correção pertencem ao Microsoft SQL Server usando o driver pyodbc.

que tal adicionar o parâmetro 'dtype'

Alguém sabe como posso implementar essa solução dentro de uma classe com uma instância self.engine?

Funciona para mim referindo-se a self.engine

Exemplo:

    self.engine = sqlalchemy.create_engine(connectionString, echo=echo)
    self.connection = self.engine.connect()

    @event.listens_for(self.engine, 'before_cursor_execute')
    def receive_before_cursor_execute(conn, cursor, statement, params, context, executemany):
        print("Listen before_cursor_execute - executemany: %s" % str(executemany))
        if executemany:
            cursor.fast_executemany = True
            cursor.commit()

Você descobriu como?

Acho que a resposta correta deveria ser usar https://docs.sqlalchemy.org/en/13/dialects/postgresql.html#psycopg2 -batch-mode-fast-execution, se você estiver tentando salvar um pandas dataframe para um postgres

Uma nova versão do pandas contém o parâmetro method que pode ser escolhido como 'multi'. Isso faz com que o código seja executado muito mais rápido.

fast_executemany pode ser executado em uma única etapa agora (sqlalchemy> = 1.3.0):

engine = sqlalchemy.create_engine(connection_string, fast_executemany=True)

Talvez valha a pena mencioná-lo em algum lugar nos documentos ou com um exemplo? É um caso particular não relacionado a pandas, mas é uma pequena adição que pode melhorar drasticamente o desempenho em muitos casos.

Uma nova versão do pandas contém o parâmetro method que pode ser escolhido como 'multi'. Isso faz com que o código seja executado muito mais rápido.

Você pensaria que definir o parâmetro chunksize seria o suficiente para fazer to_sql inserir em lote, mas não.

Uma alternativa para usuários do MS SQL é usar também turbodbc.Cursor.insertmanycolumns , expliquei isso na postagem StackOverflow vinculada: https://stackoverflow.com/a/62671681/1689261

Para leitores futuros, há duas opções para usar um 'batch_mode' para to_sql. A seguir estão as duas combinações:

create_engine(connection_string, executemany_mode='batch', executemany_batch_page_size=x)

ou

create_engine(connection_string, executemany_mode='values', executemany_values_page_size=x)

Detalhes sobre esses argumentos podem ser encontrados aqui: https://docs.sqlalchemy.org/en/13/dialects/postgresql.html#psycopg2 -fast-execution-helpers

Para usuários de postgres, recomendo definir method como um callable:

chamável com assinatura (pd_table, conn, keys, data_iter): Isso pode ser usado para implementar um método de inserção de melhor desempenho com base em recursos de dialeto de backend específicos.

e chame a função do código de exemplo aqui https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html#insertion -method e

Usar COPY FROM é realmente muito mais rápido 🚀

Esta página foi útil?
0 / 5 - 0 avaliações