Pandas: Use inserções de várias linhas para acelerar em massa em to_sql em conexões de alta latência

Criado em 1 dez. 2014  ·  48Comentários  ·  Fonte: pandas-dev/pandas

Eu tenho tentado inserir ~ 30k linhas em um banco de dados mysql usando pandas-0.15.1, oursql-0.9.3.1 e sqlalchemy-0.9.4. Como a máquina está do outro lado do Atlântico, chamar data.to_sql estava demorando mais de 1 hora para inserir os dados. Ao inspecionar com o wireshark, o problema é que ele está enviando uma inserção para cada linha, aguardando o ACK antes de enviar o próximo e, para encurtar a história, os tempos de ping estão me matando.

No entanto, seguindo as instruções do SQLAlchemy , alterei

def _execute_insert(self, conn, keys, data_iter):
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement(), data)

para

def _execute_insert(self, conn, keys, data_iter):
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement().values(data))

e toda a operação é concluída em menos de um minuto. (Para economizar um clique, a diferença é entre várias chamadas para insert into foo (columns) values (rowX) e uma massiva insert into foo (columns) VALUES (row1), (row2), row3) ). Dada a frequência com que as pessoas provavelmente usarão pandas para inserir grandes volumes de dados, isso parece uma grande vitória que seria ótimo ser incluído mais amplamente.

Alguns desafios:

  • Nem todos os bancos de dados suportam inserções de várias linhas (o SQLite e o SQLServer não suportavam no passado, mas agora suportam). Eu não sei como verificar isso via SQLAlchemy
  • O servidor MySQL que eu estava usando não me permitia inserir os dados de uma só vez, tive que definir o tamanho do bloco (5k funcionou bem, mas acho que os 30k completos foram demais). Se fizermos disso a inserção padrão, a maioria das pessoas teria que adicionar um tamanho de bloco (o que pode ser difícil de calcular, pois pode ser determinado pelo tamanho máximo do pacote do servidor).

A maneira mais fácil de fazer isso seria adicionar um parâmetro booleano multirow= (padrão False ) à função to_sql e deixar o usuário responsável por definir o tamanho do bloco, mas talvez haja uma maneira melhor?

Pensamentos?

IO SQL Performance

Comentários muito úteis

Descobrimos como fazer um patch de macaco - pode ser útil para outra pessoa. Tenha este código antes de importar pandas.

from pandas.io.sql import SQLTable

def _execute_insert(self, conn, keys, data_iter):
    print "Using monkey-patched _execute_insert"
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement().values(data))

SQLTable._execute_insert = _execute_insert

Todos 48 comentários

Isso parece razoável. Obrigado por investigar isso!

Para a implementação, dependerá de como o sqlalchemy lida com os tipos de banco de dados que não suportam isso (não posso testar isso no momento, mas parece que o sqlalchemy gera um erro (por exemplo, http://stackoverflow.com/questions/ 23886764/multiple-insert-statements-in-mssql-with-sqlalchemy). Além disso, se tiver como consequência que muitas pessoas terão que definir o tamanho do bloco, isso não é realmente uma boa ideia fazer como padrão (a menos que definamos chunksize para um valor por padrão).
Então, adicionar uma palavra-chave parece ser melhor.

@artemyk @mangecoeur @hayd @danielballan

Aparentemente SQLAlchemy tem um sinalizador dialect.supports_multivalues_insert (veja por exemplo http://pydoc.net/Python/SQLAlchemy/0.8.3/sqlalchemy.sql.compiler/ , possivelmente chamado supports_multirow_insert em outras versões, https ://www.mail-archive.com/[email protected]/msg202880.html ).

Como isso tem o potencial de acelerar muito as inserções, e podemos verificar o suporte facilmente, estou pensando que talvez possamos fazer isso por padrão e também definir o tamanho do bloco para um valor padrão (por exemplo, pedaços de 16kb ... não tenho certeza do que é muito grande na maioria das situações). Se a inserção de várias linhas falhar, poderíamos lançar uma exceção sugerindo diminuir o tamanho do bloco?

Agora eu só preciso convencer o pessoal do SQLAlchemy a definir supports_multivalues_insert como true no SQL Server > 2005 (eu invadi o código e funciona bem, mas não está ativado por padrão).

Em uma nota mais sobre o tópico, acho que o tamanho do pedaço pode ser complicado. Na minha configuração do mysql (que provavelmente configurei para permitir pacotes grandes), posso definir chunksize = 5000, na minha configuração do SQLServer, 500 era muito grande, mas 100 funcionou bem. No entanto, provavelmente é verdade que a maioria dos benefícios dessa técnica vem da inserção de 1 linha de cada vez para 100, em vez de 100 para 1000.

E se chunksize=None significasse "Escolher um tamanho de bloco de forma adaptável"? Tente algo como 5000, 500, 50, 1. Os usuários podem desativar isso especificando um tamanho de bloco. Se a sobrecarga dessas tentativas for muito grande, eu gosto da sugestão do @maxgrenderjones : chunksize=10 é um padrão melhor que chunksize=1 .

Nesse último comentário " chunksize=10 é um padrão melhor do que chunksize=1 " -> isso não é totalmente verdade, eu acho. A situação atual é fazer uma instrução de execução _one_ que consiste em instruções de inserção de linha única de várias linhas (que não é um pedaço de 1), enquanto chunksize=10 significaria fazer muitas instruções de execução a cada vez que uma linha de várias linhas inserir.
E não sei se isso é necessariamente mais rápido, mas depende muito da situação. Por exemplo com o código atual e com um banco de dados sqlite local:

In [4]: engine = create_engine('sqlite:///:memory:') #, echo='debug')

In [5]: df = pd.DataFrame(np.random.randn(50000, 10))

In [6]: %timeit df.to_sql('test_default', engine, if_exists='replace')
1 loops, best of 3: 956 ms per loop

In [7]: %timeit df.to_sql('test_default', engine, if_exists='replace', chunksize=10)
1 loops, best of 3: 2.23 s per loop

Mas é claro que isso não usa o recurso de várias linhas

Descobrimos como fazer um patch de macaco - pode ser útil para outra pessoa. Tenha este código antes de importar pandas.

from pandas.io.sql import SQLTable

def _execute_insert(self, conn, keys, data_iter):
    print "Using monkey-patched _execute_insert"
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement().values(data))

SQLTable._execute_insert = _execute_insert

Talvez possamos começar adicionando esse recurso por meio de uma nova palavra-chave multirow=True (com um padrão de False por enquanto), e depois podemos sempre ver se podemos habilitá-lo por padrão?

@maxgrenderjones @nhockham interessado em fazer um PR para adicionar isso?

@jorisvandenbossche Acho arriscado começar a adicionar argumentos de palavras-chave para abordar perfis de desempenho específicos. Se você pode garantir que é mais rápido em todos os casos (se necessário, determinando o melhor método com base nas entradas), então você não precisa de um sinalizador.

Diferentes configurações de banco de dados podem ter diferentes otimizações de desempenho (diferentes perfis de desempenho de banco de dados, local vs rede, memória grande vs SSD rápido, etc, etc), se você começar a adicionar sinalizadores de palavras-chave para cada um, torna-se uma bagunça.

Sugiro criar subclasses de SQLDatabase e SQLTable para abordar implementações específicas de desempenho, elas seriam usadas por meio da API orientada a objetos. Talvez um método de "comutação de back-end" possa ser adicionado, mas, francamente, usar a API OO é muito simples, então isso provavelmente é um exagero para o que já é um caso de uso especializado.

Eu criei uma subclasse para carregar grandes conjuntos de dados no Postgres (na verdade, é muito mais rápido salvar dados em CSV e usar os comandos COPY FROM sql não padrão do que usar inserções, consulte https://gist.github. com/mangecoeur/1fbd63d4758c2ba0c470#file-pandas_postgres-py). Para usar basta fazer PgSQLDatabase(engine, <args>).to_sql(frame, name,<kwargs>)

Apenas para referência, tentei executar o código por @jorisvandenbossche (post de 3 de dezembro) usando o recurso multirow. É um pouco mais lento. Portanto, as trocas de velocidade aqui não são triviais:

In [4]: engine = create_engine('sqlite:///:memory:') #, echo='debug')

In [5]: df = pd.DataFrame(np.random.randn(50000, 10))

In [6]: 

In [6]: %timeit df.to_sql('test_default', engine, if_exists='replace')
1 loops, best of 3: 1.05 s per loop

In [7]: 

In [7]: from pandas.io.sql import SQLTable

In [8]: 

In [8]: def _execute_insert(self, conn, keys, data_iter):
   ...:         data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
   ...:         conn.execute(self.insert_statement().values(data))
   ...:     

In [9]: SQLTable._execute_insert = _execute_insert

In [10]: 

In [10]: reload(pd)
Out[10]: <module 'pandas' from '/usr/local/lib/python2.7/site-packages/pandas/__init__.pyc'>

In [11]: 

In [11]: %timeit df.to_sql('test_default', engine, if_exists='replace', chunksize=10)
1 loops, best of 3: 9.9 s per loop

Além disso, concordo que adicionar parâmetros de palavras-chave é arriscado. No entanto, o recurso de várias linhas parece bastante fundamental. Além disso, 'patching de macaco' provavelmente não é mais robusto para alterações de API do que parâmetros de palavras-chave.

É como eu suspeitava. O patch de macaco não é a solução que eu estava sugerindo - em vez disso, enviamos várias subclasses orientadas ao desempenho que o usuário informado poderia usar através da interface OO (para evitar carregar a API funcional com muitas opções)

-----Mensagem original-----
De: "Artemy Kolchinsky" [email protected]
Enviado: ‎26/‎02/‎2015 17:13
Para: "pydata/pandas" [email protected]
Cc: "mangecoeur" jon. [email protected]
Assunto: Re: [pandas] Use inserções de várias linhas para acelerar em massa em conexões de alta latência to_sqlover (#8953)

Apenas para referência, tentei executar o código por @jorisvandenbossche (post de 3 de dezembro) usando o recurso multirow. É um pouco mais lento. Portanto, as trocas de velocidade aqui não são triviais:
Em [4]: ​​engine = create_engine('sqlite:///:memory:') #, echo='debug')

Em [5]: df = pd.DataFrame(np.random.randn(50000, 10))

Em [6]:

Em [6]: %timeit df.to_sql('test_default', engine, if_exists='replace')
1 loop, melhor de 3: 1,05 s por loop

Em [7]:

Em [7]: de pandas.io.sql import SQLTable

Em [8]:

Em [8]: def _execute_insert(self, conn, keys, data_iter):
...: data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
...: conn.execute(self.insert_statement().values(data))
...:

Em [9]: SQLTable._execute_insert = _execute_insert

Em [10]:

Em [10]: recarregar(pd)
Fora[10]:

Em [11]:

Em [11]: %timeit df.to_sql('test_default', engine, if_exists='replace', chunksize=10)
1 loop, melhor de 3: 9,9 s por loop
Além disso, concordo que adicionar parâmetros de palavras-chave é arriscado. No entanto, o recurso de várias linhas parece bastante fundamental. Além disso, 'patching de macaco' provavelmente não é mais robusto para alterações de API do que parâmetros de palavras-chave.

Responda a este e-mail diretamente ou visualize-o no GitHub.

De acordo com o título inicial do ticket, não acho que essa abordagem seja preferível em todos os casos, portanto, não a tornaria o padrão. No entanto, sem ele, os pandas to_sql inutilizáveis ​​para mim, por isso é importante o suficiente para eu continuar solicitando a alteração. (Também se tornou a primeira coisa que mudo quando atualizo minha versão do pandas). Quanto aos valores sensatos de chunksize , não acho que exista um n verdadeiro, pois o tamanho do pacote dependerá de quantas colunas existem (e o que há nelas) de maneiras difíceis de prever . Infelizmente, o SQLServer falha com uma mensagem de erro que parece totalmente não relacionada (mas não é) se você definir chunksize muito alto (o que provavelmente é o motivo pelo qual as inserções de várias linhas não são ativadas, exceto com um patch no SQLAlchemy), mas funciona bem com mysql . Os usuários podem precisar experimentar para determinar qual valor de n provavelmente resultará em um tamanho de pacote aceitavelmente grande (para qualquer que seja o banco de dados de apoio). Ter pandas escolhendo n provavelmente nos levará muito mais longe nos detalhes de implementação do que queremos (ou seja, na direção oposta da abordagem SQLALchemy de máxima abstração possível)

Resumindo, minha recomendação seria adicioná-la como palavra-chave, com alguns comentários úteis sobre como usá-la. Esta não seria a primeira vez que uma palavra-chave foi usada para selecionar uma implementação (consulte: http://pandas.pydata.org/pandas-docs/dev/generated/pandas.DataFrame.apply.html), mas talvez não seja t o melhor exemplo, pois não tenho a menor idéia do que raw= significa, mesmo tendo lido a explicação!

Percebi que ele também consome uma enorme quantidade de memória. Como um DataFrame de 1,6+ GB com cerca de 700.000 linhas e 301 colunas requer quase 34 GB durante a inserção! Isso é como por cima ineficiente. Alguma idéia de por que isso pode ser o caso? Aqui está um clipe de tela:

image

Oi pessoal,
algum progresso nesta questão?

Estou tentando inserir cerca de 200 mil linhas usando to_sql, mas demora uma eternidade e consome uma enorme quantidade de memória! Usar chuncksize ajuda com a memória, mas ainda assim a velocidade é muito lenta.

Minha impressão, olhando para o rastreamento do MSSQL DBase, é que a inserção é realmente executada uma linha por vez.

A única abordagem viável agora é despejar em um arquivo csv em uma pasta compartilhada e usar BULK INSERT. Mas é muito chato e deselegante!

@andreacassioli Você pode usar odo para inserir um DataFrame em um banco de dados SQL por meio de um arquivo CSV intermediário. Consulte Carregando CSVs em Bancos de Dados SQL .

Eu não acho que você pode chegar nem perto do desempenho BULK INSERT usando ODBC.

@ostrokach obrigado, na verdade estou usando arquivos csv agora. Se eu pudesse chegar perto, trocaria um pouco de tempo pela simplicidade!

Achei que isso poderia ajudar alguém:
http://docs.sqlalchemy.org/en/latest/faq/performance.html#i -m-inserting-400-000-rows-with-the-orm-and-it-s-really-slow

@indera pandas não usa o ORM, apenas sqlalchemy Core (que é o que a entrada doc sugere usar para inserções grandes)

há algum consenso sobre como contornar isso nesse meio tempo? Estou inserindo vários milhões de linhas no postgres e demora uma eternidade. O CSV / odo é o caminho a seguir?

@russlamb uma maneira prática de resolver esse problema é simplesmente fazer upload em massa. No entanto, este é alguém específico do banco de dados, então odo tem soluções para postgresl (e pode ser mysql ) eu acho. para algo como sqlserver você tem que 'fazer isso sozinho' (IOW você tem que escrevê-lo).

Para sqlserver eu usei o driver FreeTDS (http://www.freetds.org/software.html e https://github.com/mkleehammer/pyodbc ) com entidades SQLAlchemy que resultaram em inserções muito rápidas (20K linhas por data frame) :

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()


class DemographicEntity(Base):
    __tablename__ = 'DEMOGRAPHIC'

    patid = db.Column("PATID", db.Text, primary_key=True)
    """
    patid = db.Column("PATID", db.Text, primary_key=True, autoincrement=False, nullable=True)
    birth_date = db.Column("BIRTH_DATE", db.Date)
    birth_time = db.Column("BIRTH_TIME", db.Text(5))
    sex = db.Column("SEX", db.Text(2))

def get_db_url(db_host, db_port, db_name, db_user, db_pass):
    params = parse.quote(
        "Driver={{FreeTDS}};Server={};Port={};"
        "Database={};UID={};PWD={};"
        .format(db_host, db_port, db_name, db_user, db_pass))
    return 'mssql+pyodbc:///?odbc_connect={}'.format(params)

def get_db_pool():
    """
    Create the database engine connection.
    <strong i="6">@see</strong> http://docs.sqlalchemy.org/en/latest/core/engines.html

    :return: Dialect object which can either be used directly
            to interact with the database, or can be passed to
            a Session object to work with the ORM.
    """
    global DB_POOL

    if DB_POOL is None:
        url = get_db_url(db_host=DB_HOST, db_port=DB_PORT, db_name=DB_NAME,
                         db_user=DB_USER, db_pass=DB_PASS)
        DB_POOL = db.create_engine(url,
                                   pool_size=10,
                                   max_overflow=5,
                                   pool_recycle=3600)

    try:
        DB_POOL.execute("USE {db}".format(db=DB_NAME))
    except db.exc.OperationalError:
        logger.error('Database {db} does not exist.'.format(db=DB_NAME))

    return DB_POOL


def save_frame():
    db_pool = get_db_pool()
    records = df.to_dict(orient='records')
    result = db_pool.execute(entity.__table__.insert(), records)

O CSV / odo é o caminho a seguir?

Esta solução quase sempre será mais rápida, eu acho, independentemente das configurações de várias linhas / tamanho do bloco.

Mas, @russlamb , é sempre interessante saber se essa palavra-chave de várias linhas seria uma melhoria no seu caso. Veja, por exemplo, https://github.com/pandas-dev/pandas/issues/8953#issuecomment -76139975 para testar isso facilmente.

Acho que há um consenso de que queremos ter uma maneira de especificar isso (sem necessariamente alterar o padrão). Então, se alguém quiser fazer um PR para isso, certamente será bem-vindo.
Houve apenas alguma discussão sobre como adicionar essa habilidade (nova palavra-chave vs subclasse usando API OO).

@jorisvandenbossche O documento que vinculei acima menciona "Como alternativa, o SQLAlchemy ORM oferece o conjunto de métodos de operações em massa, que fornece ganchos em subseções da unidade de processo de trabalho para emitir construções INSERT e UPDATE no nível do núcleo com um pequeno grau de ORM automação baseada."

O que estou sugerindo é implementar uma versão específica do sqlserver para to_sql que usa os ORMs SQLAlchemy para acelerar como no código que postei acima.

Isso foi proposto antes. O jeito que você vai é implementar um sql pandas
classe otimizada para um back-end. Eu postei uma essência no passado para usar
comando postgres COPY FROM que é muito mais rápido. Porém algo semelhante
agora está disponível em odo, e construído de forma mais robusta. Não há muito
ponto IMHO na duplicação do trabalho de odo.

Em 7 de março de 2017 00:53, "Andrei Sura" [email protected] escreveu:

@jorisvandenbossche https://github.com/jorisvandenbossche O documento
Eu vinculei as menções acima "Como alternativa, o SQLAlchemy ORM oferece o Bulk
Conjunto de métodos de operações, que fornece ganchos em subseções do
unidade de processo de trabalho para emitir INSERT e UPDATE de nível central
construções com um pequeno grau de automação baseada em ORM."

O que estou sugerindo é implementar uma versão específica do sqlserver para
"to_sql" que sob o capô usa o núcleo SQLAlchemy para acelerar.


Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/pandas-dev/pandas/issues/8953#issuecomment-284437587 ,
ou silenciar o thread
https://github.com/notifications/unsubscribe-auth/AAtYVDXKLuTlsh9ycpMQvU5C0hs_RxuYks5rjCwBgaJpZM4DCjLh
.

Também notei que você mencionou que o sqlalchemy poderia core. A menos que algo
mudou muito, apenas o núcleo sqlalchemy é usado em qualquer caso, sem orm. Se você
quer acelerar mais do que usar o core você tem que ir para o nível mais baixo, db
otimização específica

Em 7 de março de 2017 00:53, "Andrei Sura" [email protected] escreveu:

@jorisvandenbossche https://github.com/jorisvandenbossche O documento
Eu vinculei as menções acima "Como alternativa, o SQLAlchemy ORM oferece o Bulk
Conjunto de métodos de operações, que fornece ganchos em subseções do
unidade de processo de trabalho para emitir INSERT e UPDATE de nível central
construções com um pequeno grau de automação baseada em ORM."

O que estou sugerindo é implementar uma versão específica do sqlserver para
"to_sql" que sob o capô usa o núcleo SQLAlchemy para acelerar.


Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/pandas-dev/pandas/issues/8953#issuecomment-284437587 ,
ou silenciar o thread
https://github.com/notifications/unsubscribe-auth/AAtYVDXKLuTlsh9ycpMQvU5C0hs_RxuYks5rjCwBgaJpZM4DCjLh
.

Isso está sendo consertado/cuidado? A partir de agora, inserir dataframes de pandas em um banco de dados SQL é extremamente lento, a menos que seja um dataframe de brinquedo. Vamos decidir sobre uma solução e empurrá-la para a frente?

@dfernan Como mencionado acima, você pode querer olhar para o odo . Usar um arquivo CSV intermediário sempre será muito mais rápido do que passar por sqlalchemy, não importa que tipo de melhorias aconteçam aqui ...

@ostrokach , não estou convencido de que o comportamento do odo seja o que o usuário típico do Pandas deseja. Inserções de várias linhas sobre ODBC provavelmente são rápidas o suficiente para a maioria dos analistas.

Para falar por mim, passei apenas algumas horas mudando do patch de macaco acima para odo. O tempo de execução dos pandas simples foi de mais de 10 horas, RBAR. O patch de macaco é executado em 2 no mesmo conjunto de dados.
A rota odo/CSV foi mais rápida, como esperado, mas não o suficiente para valer a pena o esforço. Eu brinquei com problemas de conversão de CSV com os quais não me importava muito, tudo em nome de evitar o patch de macaco. Estou importando 250 mil linhas de ~ 10 bancos de dados mysql e PG em uma área comum no Postgres, para análise de NLP.

Estou intimamente familiarizado com as abordagens de carregamento em massa dos cônjuges de odo. Eu os uso há anos onde estou começando com dados CSV. Existem limitações importantes para eles:

  1. Para o caso df->CSV->Postgres, o acesso ao shell e uma etapa scp são necessários para obter o CSV no host PG. Parece que @mangecoeur contornou isso com um stream para STDIN.
  2. Para o meu propósito (250 mil linhas de comentários, com muitos casos especiais no conteúdo do texto), lutei para acertar os parâmetros CSV. Eu não queria ganhos de desempenho o suficiente para continuar investindo nisso.

Eu volto para o patch, para que eu possa começar o trabalho de análise.

Concordo com @jorisvandenbossche , @maxgrenderjones. Uma opção (não um padrão) para escolher isso seria imensamente útil. O ponto de @artemyk sobre dialect.supports_multivalues_insert pode até tornar isso um padrão razoável.

Fico feliz em enviar um PR se isso levar isso adiante.

apenas para adicionar minha experiência com odo, ele não funcionou para inserções em massa do MS Sql devido a um problema conhecido com a codificação. imho m-row insert são uma boa solução prática para a maioria das pessoas.

@markschwarz uma opção para permitir que isso funcione mais rápido seria muito bem-vinda!

Rastreando as consultas usando o sqlite, pareço estar entrando em várias inserções ao usar chunksize :

2017-09-28 00:21:39,007 INFO sqlalchemy.engine.base.Engine INSERT INTO country_hsproduct_year (location_id, product_id, year, export_rca, import_value, cog, export_value, distance, location_level, product_level) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2017-09-28 00:21:39,007 INFO sqlalchemy.engine.base.Engine ((75, 1237, 1996, 1.7283086776733398, 273487116.0, 0.0, 514320160.0, 0.5413745641708374, 'country', '4digit'), (75, 1237, 1997, 1.7167805433273315, 312047528.0, 0.0, 592372864.0, 0.5314807891845703, 'country', '4digit'), (75, 1237, 1998, 1.2120152711868286, 341676961.0, 0.0, 468860608.0, 0.5472233295440674, 'country', '4digit'), (75, 1237, 1999, 1.236651062965393, 334604240.0, 0.0, 440722336.0, 0.5695921182632446, 'country', '4digit'), (75, 1237, 2000, 1.189828872680664, 383555023.0, 0.0, 426384832.0, 0.5794379711151123, 'country', '4digit'), (75, 1237, 2001, 0.9920380115509033, 374157144.0, 0.3462945520877838, 327031392.0, 0.6234743595123291, 'country', '4digit'), (75, 1237, 2002, 1.0405025482177734, 471456583.0, 0.0, 377909376.0, 0.6023964285850525, 'country', '4digit'), (75, 1237, 2003, 1.147829532623291, 552441401.0, 0.0, 481313504.0, 0.5896202325820923, 'country', '4digit')  ... displaying 10 of 100000 total bound parameter sets ...  (79, 1024, 2015, 0.0, None, 0.8785018920898438, 0.0, 0.9823430776596069, 'country', '4digit'), (79, 1025, 1995, 0.0, None, 0.5624096989631653, 0.0, 0.9839603304862976, 'country', '4digit'))

(sem o patch de macaco, isto é)

Curiosamente, com o patch de macaco, ele quebra quando eu dou um tamanho de 10 ^ 5, mas não 10 ^ 3. O erro é "muitas variáveis ​​sql" no sqlite.

@makmanalp , ainda não rastreei no PG para verificar esse comportamento, mas quase sempre defino o tamanho do bloco na inserção. No meu exemplo acima, defini aleatoriamente para 5 valores entre 200-5000. Eu não vi diferenças drásticas de tempo decorrido entre essas escolhas, sem o patch de macaco. Com o patch, o tempo decorrido caiu ~80%.

https://github.com/pandas-dev/pandas/issues/8953#issuecomment -76139975

Este patch de macaco ainda está funcionando? Eu tentei no MS SQL Server, mas não vi uma melhoria. Além disso, ele lança uma exceção:

(pyodbc.Error) ('07002', '[07002] [Microsoft][SQL Server Native Client 11.0]COUNT field incorrect or syntax error (0) (SQLExecDirectW)')

@hangyao Acho que esse patch é específico da implementação, uma daquelas coisas que o python DBAPI deixa para o driver DBAPI sobre como lidar. Então pode ser mais rápido ou não. RE: o erro de sintaxe, não tenho certeza sobre isso.

Estou pensando em adicionar a função abaixo no arquivo /io/sql.py na linha 507 dentro da função _engine_builder dentro de uma nova clouse IF na linha 521 tornando o 'novo' _engine_builder abaixo trecho. Testei-o brevemente em meu ambiente e funciona muito bem para bancos de dados MSSQL, alcançando acelerações > 100x. Ainda não testei em outros bancos de dados.

A coisa de me impedir de fazer um PR é que eu acho que é mais esforço para torná-lo limpo e seguro do que apenas inseri-lo como abaixo, isso pode nem sempre ser a especificação desejada e adicionar uma chave booleana, que ativa / desativa essa configuração , (por exemplo fast_executemany=True ) no to_sql parecia um esforço um pouco grande demais para fazer sem perguntar, eu acho.

Então minhas perguntas são:

  • A função abaixo funciona e também aumenta a velocidade de INSERT para o PostgreSQL?

  • O evento pandas quer esse snippet em sua fonte? Se for assim:

  • Deseja-se adicionar esta função à funcionalidade padrão sql.py ou existe um lugar melhor para adicionar isso?

Adoro ouvir alguns comentários.

Fonte para a resposta: https://stackoverflow.com/questions/48006551/speeding-up-pandas-dataframe-to-sql-with-fast-executemany-of-pyodbc/48861231#48861231

@event.listens_for(engine, 'before_cursor_execute')
def receive_before_cursor_execute(conn, cursor, statement, params, context, executemany):
    if executemany:
        cursor.fast_executemany = True
def _engine_builder(con):
    """
    Returns a SQLAlchemy engine from a URI (if con is a string)
    else it just return con without modifying it.
    """
    global _SQLALCHEMY_INSTALLED
    if isinstance(con, string_types):
        try:
            import sqlalchemy
        except ImportError:
            _SQLALCHEMY_INSTALLED = False
        else:
            con = sqlalchemy.create_engine(con)

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

@tsktsktsk123 Recentemente houve uma fusão de relações públicas relacionada a isso: https://github.com/pandas-dev/pandas/pull/19664. Eu ainda não olhei em detalhes para o seu post, e certamente não é exatamente o mesmo (ele usa o atributo supports_multivalues_insert do mecanismo sqlalchemy), mas apenas para ter certeza de que você está ciente disso, caso isso já ajude também.

Essas são ótimas notícias! Eu não olhei para o PR, mas vou compará-lo neste fim de semana e voltar com os resultados. Obrigado pela atenção.

Eu estava apenas tentando 0.23.0 RC2 (no postgresql) e em vez de ter um aumento de desempenho meu script ficou significativamente mais lento. A consulta de banco de dados ficou muito mais rápida, mas medindo o tempo para to_sql() , na verdade, ficou até 1,5 vezes mais lenta (como de 7 a 11 segundos) ...

Não tenho certeza se a desaceleração vem deste PR, pois acabei de testar o RC.

Alguém mais passou pelo mesmo problema?

@schettino72 Quantos dados você estava inserindo?

Cerca de 30 mil linhas com 10 colunas. Mas realmente quase tudo que eu tento é mais lento (o SQL é mais rápido, mas no geral mais lento). Ele está criando uma enorme instrução SQL onde há interpolação de valor para TODOS os valores. Algo como

 %(user_id_m32639)s, %(event_id_m32639)s, %(colx_m32639)s,

Achei d6tstack muito mais simples de usar, é um one-liner d6tstack.utils.pd_to_psql(df, cfg_uri_psql, 'benchmark', if_exists='replace') e é muito mais rápido que df.to_sql() . Suporta postgres e mysql. Veja https://github.com/d6t/d6tstack/blob/master/examples-sql.ipynb

Eu tenho usado a solução Monkey Patch:

from pandas.io.sql import SQLTable

def _execute_insert(self, conn, keys, data_iter):
    print "Using monkey-patched _execute_insert"
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement().values(data))

SQLTable._execute_insert = _execute_insert

há algum tempo, mas agora estou recebendo um erro:

TypeError: insert_statement() missing 2 required positional arguments: 'data' and 'conn'

Tem mais alguém pegando isso? Estou no Python 3.6.5 (Anaconda) e pandas==0.23.0

isso está sendo consertado? Atualmente, df.to_sql é extremamente lento e não pode ser usado para muitos casos de uso práticos. O projeto Odo parece já ter sido abandonado.
Eu tenho os seguintes casos de uso em séries temporais financeiras em que df.to_sql é praticamente inutilizável:
1) copiando dados csv históricos para o banco de dados postgres - não pode usar df.to_sql e teve que usar código personalizado em torno da funcionalidade copy_from psycopg2
2) dados de streaming (chegando em um lote de ~500-3000 linhas por segundo) para serem despejados no banco de dados postgres - novamente o desempenho do df.to_sql é bastante decepcionante, pois está demorando muito tempo para inserir esses lotes naturais de dados no postgres.
O único lugar onde acho o df.to_sql útil agora é criar tabelas automaticamente!!! - que não é o caso de uso para o qual foi projetado.
Não tenho certeza se outras pessoas também compartilham a mesma preocupação, mas esse problema precisa de alguma atenção para que as interfaces "dataframes-to-database" funcionem sem problemas.
Esperar ansiosamente.

Ei, estou recebendo este erro quando tento executar uma inserção múltipla em um banco de dados SQLite:

Este é o meu código:
df.to_sql("financial_data", con=conn, if_exists="append", index=False, method="multi")

e recebo este erro:

Traceback (most recent call last):

  File "<ipython-input-11-cf095145b980>", line 1, in <module>
    handler.insert_financial_data_from_df(data, "GOOG")

  File "C:\Users\user01\Documents\Code\FinancialHandler.py", line 110, in insert_financial_data_from_df
    df.to_sql("financial_data", con=conn, if_exists="append", index=False, method="multi")

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\core\generic.py", line 2531, in to_sql
    dtype=dtype, method=method)

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\io\sql.py", line 460, in to_sql
    chunksize=chunksize, dtype=dtype, method=method)

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\io\sql.py", line 1547, in to_sql
    table.insert(chunksize, method)

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\io\sql.py", line 686, in insert
    exec_insert(conn, keys, chunk_iter)

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\io\sql.py", line 609, in _execute_insert_multi
    conn.execute(self.table.insert(data))

TypeError: insert() takes exactly 2 arguments (1 given)

Por que isso está acontecendo? Estou usando Python 3.7.3 (Anaconda), pandas 0.24.2 e sqlite3 2.6.0.

Muito obrigado antecipadamente!

@jconstanzo você pode abrir isso como um novo problema?
E se possível, você pode tentar fornecer um exemplo reproduzível? (por exemplo, um pequeno dataframe de exemplo que pode mostrar o problema)

@jconstanzo Tendo o mesmo problema aqui. Usar method='multi' (no meu caso, em combinação com chunksize ) parece acionar esse erro quando você tenta inserir em um banco de dados SQLite.

Infelizmente, não posso fornecer um dataframe de exemplo porque meu conjunto de dados é enorme, é por isso que estou usando method e chunksize em primeiro lugar.

Desculpe-me pela demora. Acabei de abrir um problema para este problema: https://github.com/pandas-dev/pandas/issues/29921

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