Pandas: Use inserciones de varias filas para aceleraciones masivas en to_sql en conexiones de alta latencia

Creado en 1 dic. 2014  ·  48Comentarios  ·  Fuente: pandas-dev/pandas

He intentado insertar ~30k filas en una base de datos mysql usando pandas-0.15.1, oursql-0.9.3.1 y sqlalchemy-0.9.4. Debido a que la máquina está al otro lado del Atlántico, llamar a data.to_sql tomó >1 hora para insertar los datos. Al inspeccionar con wireshark, el problema es que envía una inserción para cada fila, luego espera el ACK antes de enviar el siguiente y, para resumir, los tiempos de ping me están matando.

Sin embargo, siguiendo las instrucciones de SQLAlchemy , cambié

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))

y toda la operación se completa en menos de un minuto. (Para ahorrarle un clic, la diferencia es entre varias llamadas a insert into foo (columns) values (rowX) y una enorme insert into foo (columns) VALUES (row1), (row2), row3) ). Dada la frecuencia con la que es probable que las personas usen pandas para insertar grandes volúmenes de datos, esto se siente como una gran victoria que sería genial incluirlo más ampliamente.

Algunos desafíos:

  • No todas las bases de datos admiten inserciones de varias filas (SQLite y SQLServer no lo hacían en el pasado, aunque ahora sí). No sé cómo verificar esto a través de SQLAlchemy
  • El servidor MySQL que estaba usando no me permitía insertar todos los datos de una sola vez, tuve que establecer el tamaño de fragmento (5k funcionó bien, pero supongo que los 30k completos fueron demasiado). Si hiciéramos esto como la inserción predeterminada, la mayoría de las personas tendrían que agregar un tamaño de fragmento (que podría ser difícil de calcular, ya que podría estar determinado por el tamaño máximo de paquete del servidor).

La forma más fácil de hacer esto sería agregar un parámetro booleano multirow= (predeterminado False ) a la función to_sql , y luego dejar al usuario responsable de configurar el tamaño de fragmento, pero tal vez hay una mejor manera?

¿Pensamientos?

IO SQL Performance

Comentario más útil

Descubrimos cómo parchear a los monos: podría ser útil para otra persona. Tenga 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 comentarios

Esto parece razonable. ¡Gracias por investigar esto!

Para la implementación, dependerá de cómo sqlalchemy trate con los tipos de base de datos que no admiten esto (no puedo probar esto en este momento, pero parece que sqlalchemy genera un error (por ejemplo, http://stackoverflow.com/questions/ 23886764/multiple-insert-statements-in-mssql-with-sqlalchemy). Además, si tiene la consecuencia de que muchas personas tendrán que configurar el tamaño de fragmento, no es una buena idea hacerlo de forma predeterminada (a menos que configuremos chunksize a un valor por defecto).
Así que agregar una palabra clave parece quizás mejor.

@artemyk @mangecoeur @hayd @danielballan

Aparentemente, SQLAlchemy tiene un indicador dialect.supports_multivalues_insert (consulte, por ejemplo, http://pydoc.net/Python/SQLAlchemy/0.8.3/sqlalchemy.sql.compiler/, posiblemente llamado supports_multirow_insert en otras versiones, https ://www.mail-archive.com/[email protected]/msg202880.html ).

Dado que esto tiene el potencial de acelerar mucho las inserciones, y podemos buscar soporte fácilmente, estoy pensando que tal vez podríamos hacerlo de forma predeterminada, y también establecer el tamaño de fragmento en un valor predeterminado (por ejemplo, fragmentos de 16 kb... no estoy seguro de qué es demasiado grande en la mayoría de las situaciones). Si la inserción de filas múltiples falla, ¿podríamos lanzar una excepción que sugiera reducir el tamaño de fragmento?

Ahora solo necesito persuadir a la gente de SQLAlchemy para que establezca supports_multivalues_insert en verdadero en SQL Server> 2005 (lo pirateé en el código y funciona bien, pero no está activado de manera predeterminada).

En una nota más sobre el tema, creo que el tamaño de la porción podría ser complicado. En mi configuración de mysql (que probablemente configuré para permitir paquetes grandes), puedo configurar chunksize = 5000, en mi configuración de SQLServer, 500 era demasiado grande, pero 100 funcionó bien. Sin embargo, probablemente sea cierto que la mayoría de los beneficios de esta técnica provienen de insertar 1 fila a la vez hasta 100, en lugar de 100 a 1000.

¿Qué pasaría si chunksize=None significara "Elegir adaptativamente un tamaño de fragmento"? Intente algo como 5000, 500, 50, 1. Los usuarios pueden desactivar esto especificando un tamaño de fragmento. Si la sobrecarga de estos intentos es demasiado grande, me gusta la sugerencia de @maxgrenderjones : chunksize=10 es un valor predeterminado mejor que chunksize=1 .

En ese último comentario " chunksize=10 es un valor predeterminado mejor que chunksize=1 " -> creo que eso no es del todo cierto. La situación actual es hacer _una_ sentencia de ejecución que consiste en sentencias de inserción de una sola fila de varias líneas (que no tiene un tamaño de fragmento de 1), mientras que chunksize=10 significaría hacer muchas sentencias de ejecución con cada vez una sentencia de varias filas. insertar.
Y no sé si esto es necesariamente más rápido, pero mucho depende de la situación. Por ejemplo con el código actual y con una base de datos 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

Pero, por supuesto, esto no utiliza la función de varias filas.

Descubrimos cómo parchear a los monos: podría ser útil para otra persona. Tenga 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

Tal vez podamos comenzar agregando esta característica a través de una nueva palabra clave multirow=True (con un valor predeterminado de Falso por ahora), y luego podemos ver si podemos habilitarla de manera predeterminada.

@maxgrenderjones @nhockham ¿ interesado en hacer una RP para agregar esto?

@jorisvandenbossche Creo que es arriesgado comenzar a agregar argumentos de palabras clave para abordar perfiles de rendimiento específicos. Si puede garantizar que es más rápido en todos los casos (si es necesario, haga que determine el mejor método en función de las entradas), entonces no necesita una bandera en absoluto.

Diferentes configuraciones de base de datos pueden tener diferentes optimizaciones de rendimiento (diferentes perfiles de rendimiento de base de datos, local frente a red, gran memoria frente a SSD rápido, etc.), si comienza a agregar indicadores de palabras clave para cada uno, se convierte en un desastre.

Sugeriría crear subclases de SQLDatabase y SQLTable para abordar implementaciones específicas de rendimiento, se utilizarían a través de la API orientada a objetos. Quizás se podría agregar un método de "cambio de back-end", pero, francamente, usar la API OO es muy simple, por lo que probablemente sea excesivo para lo que ya es un caso de uso especializado.

Creé una subclase de este tipo para cargar grandes conjuntos de datos en Postgres (en realidad, es mucho más rápido guardar datos en CSV y luego usar los comandos incorporados no estándar COPY FROM sql que usar inserciones, consulte https://gist.github. com/mangecoeur/1fbd63d4758c2ba0c470#file-pandas_postgres-py). Para usarlo solo tienes que hacer PgSQLDatabase(engine, <args>).to_sql(frame, name,<kwargs>)

Solo como referencia, intenté ejecutar el código de @jorisvandenbossche (publicación del 3 de diciembre) usando la función de varias filas. Es bastante más lento. Entonces, las compensaciones de velocidad aquí no son triviales:

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

Además, estoy de acuerdo en que agregar parámetros de palabras clave es arriesgado. Sin embargo, la característica de múltiples filas parece bastante fundamental. Además, 'monkey-patching' probablemente no sea más resistente a los cambios de API que los parámetros de palabras clave.

Es como sospechaba. Monkey patching no es la solución que estaba sugiriendo, sino que enviamos una serie de subclases orientadas al rendimiento que el usuario informado podría usar a través de la interfaz OO (para evitar cargar la API funcional con demasiadas opciones)

-----Mensaje original-----
De: "Artemy Kolchinsky" [email protected]
Enviado: 26/02/2015 17:13
Para: "pydata/pandas" [email protected]
CC: "mangecoeur" jon. [email protected]
Asunto: Re: [pandas] Usar inserciones de filas múltiples para aceleraciones masivas en conexiones de alta latencia to_sqlover (#8953)

Solo como referencia, intenté ejecutar el código de @jorisvandenbossche (publicación del 3 de diciembre) usando la función de varias filas. Es bastante más lento. Entonces, las compensaciones de velocidad aquí no son triviales:
En [4]: ​​motor = create_engine('sqlite:///:memory:') #, echo='debug')

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

En [6]:

En [6]: %timeit df.to_sql('test_default', motor, if_exists='replace')
1 bucle, lo mejor de 3: 1,05 s por bucle

En [7]:

En [7]: desde pandas.io.sql import SQLTable

En [8]:

En [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))
...:

En [9]: SQLTable._execute_insert = _execute_insert

En [10]:

En [10]: recargar (pd)
Fuera[10]:

En [11]:

En [11]: %timeit df.to_sql('test_default', motor, if_exists='replace', chunksize=10)
1 bucle, lo mejor de 3: 9,9 s por bucle
Además, estoy de acuerdo en que agregar parámetros de palabras clave es arriesgado. Sin embargo, la característica de múltiples filas parece bastante fundamental. Además, 'monkey-patching' probablemente no sea más resistente a los cambios de API que los parámetros de palabras clave.

Responda a este correo electrónico directamente o véalo en GitHub.

Según el título del ticket inicial, no creo que este enfoque sea preferible en todos los casos, por lo que no lo convertiría en el predeterminado. Sin embargo, sin él, los pandas to_sql no se pueden usar, por lo que es lo suficientemente importante como para seguir solicitando el cambio. (También se ha convertido en lo primero que cambio cuando actualizo mi versión de pandas). En cuanto a los valores razonables de chunksize , no creo que haya un verdadero n , ya que el tamaño del paquete dependerá de cuántas columnas haya (y qué hay en ellas) en formas difíciles de predecir . Desafortunadamente, SQLServer falla con un mensaje de error que parece totalmente no relacionado (pero no lo está) si configura chunksize demasiado alto (que es probablemente la razón por la cual las inserciones de varias filas no están activadas excepto con un parche en SQLAlchemy), pero funciona bien con mysql . Es posible que los usuarios deban experimentar para determinar qué valor de n es probable que resulte en un tamaño de paquete aceptablemente grande (para cualquiera que sea su base de datos de respaldo). Tener pandas eligiendo n es probable que nos lleve mucho más abajo en los detalles de implementación de lo que queremos (es decir, la dirección opuesta al enfoque SQLALchemy de máxima abstracción posible)

En resumen, mi recomendación sería agregarlo como palabra clave, con algunos comentarios útiles sobre cómo usarlo. Esta no sería la primera vez que se usa una palabra clave para seleccionar una implementación (consulte: http://pandas.pydata.org/pandas-docs/dev/generated/pandas.DataFrame.apply.html), pero tal vez eso no sea así. Es el mejor ejemplo, ya que no tengo la primera idea de lo que significa raw= , ¡incluso después de haber leído la explicación!

He notado que también consume una gran cantidad de memoria. ¡Como un marco de datos de más de 1,6 GB con unas 700 000 filas y 301 columnas requiere casi 34 GB durante la inserción! Eso es como exageradamente ineficiente. ¿Alguna idea de por qué podría ser así? Aquí hay un clip de pantalla:

image

Hola chicos,
algun avance en este tema?

Intento insertar alrededor de 200 000 filas usando to_sql, ¡pero lleva una eternidad y consume una gran cantidad de memoria! El uso de chuncksize ayuda con la memoria, pero aún así la velocidad es muy lenta.

Mi impresión, mirando el seguimiento de MSSQL DBase es que la inserción en realidad se realiza una fila a la vez.

El único enfoque viable ahora es volcar a un archivo csv en una carpeta compartida y usar BULK INSERT. ¡Pero es muy molesto y poco elegante!

@andreacassioli Puede usar odo para insertar un DataFrame en una base de datos SQL a través de un archivo CSV intermediario. Consulte Cargar archivos CSV en bases de datos SQL .

No creo que pueda acercarse ni siquiera a un rendimiento de BULK INSERT con ODBC.

@ostrokach gracias, de hecho, ahora estoy usando archivos csv. ¡Si pudiera acercarme, cambiaría un poco de tiempo por simplicidad!

@indera pandas no usa el ORM, solo sqlalchemy Core (que es lo que la entrada del documento sugiere usar para inserciones grandes)

¿Hay algún consenso sobre cómo solucionar esto mientras tanto? Estoy insertando varios millones de filas en postgres y lleva una eternidad. ¿Es CSV / odo el camino a seguir?

@russlamb , una forma práctica de resolver este problema es simplemente realizar una carga masiva. Sin embargo, se trata de alguien específico de db, por lo que odo tiene soluciones para postgresl (y puede ser mysql ), creo. para algo como sqlserver tienes que 'hacer esto tú mismo' (IOW tienes que escribirlo).

Para sqlserver utilicé el controlador FreeTDS (http://www.freetds.org/software.html y https://github.com/mkleehammer/pyodbc) con entidades SQLAlchemy que resultaron en inserciones muy rápidas (20K filas por marco de datos) :

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)

¿Es CSV / odo el camino a seguir?

Creo que esta solución casi siempre será más rápida, independientemente de la configuración de varias filas/tamaño de fragmento.

Pero, @russlamb , siempre es interesante saber si una palabra clave de varias filas sería una mejora en su caso. Consulte, por ejemplo, https://github.com/pandas-dev/pandas/issues/8953#issuecomment -76139975 sobre una manera de probar esto fácilmente.

Creo que hay acuerdo en que queremos tener una forma de especificar esto (sin cambiar necesariamente el valor predeterminado). Entonces, si alguien quiere hacer relaciones públicas para esto, ciertamente es bienvenido.
Solo hubo alguna discusión sobre cómo agregar esta capacidad (nueva palabra clave frente a subclase usando OO api).

@jorisvandenbossche El documento que vinculé anteriormente menciona "Alternativamente, SQLAlchemy ORM ofrece el conjunto de métodos de operaciones masivas, que proporcionan enlaces a subsecciones del proceso de unidad de trabajo para emitir construcciones INSERT y UPDATE de nivel central con un pequeño grado de ORM automatización basada en ".

Lo que sugiero es implementar una versión específica de sqlserver para to_sql que, bajo el capó, usa los ORM de SQLAlchemy para acelerar, como en el código que publiqué anteriormente.

Esto fue propuesto antes. La forma de hacerlo es implementar un pandas sql
clase optimizada para un backend. Publiqué una esencia en el pasado para usar
postgres COPY FROM comando que es mucho más rápido. Sin embargo algo similar
ahora está disponible en odo y construido de una manera más robusta. no hay mucho
punto en mi humilde opinión en la duplicación de trabajo de odo.

El 7 de marzo de 2017 a las 00:53, "Andrei Sura" [email protected] escribió:

@jorisvandenbossche https://github.com/jorisvandenbossche El documento
Enlacé las menciones anteriores "Alternativamente, SQLAlchemy ORM ofrece el Bulk
Conjunto de métodos de operaciones, que proporciona enlaces a las subsecciones del
unidad de proceso de trabajo para emitir INSERTAR y ACTUALIZAR de nivel central
construcciones con un pequeño grado de automatización basada en ORM".

Lo que sugiero es implementar una versión específica de sqlserver para
"to_sql", que bajo el capó utiliza el núcleo de SQLAlchemy para acelerar.


Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/pandas-dev/pandas/issues/8953#issuecomment-284437587 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AAtYVDXKLuTlsh9ycpMQvU5C0hs_RxuYks5rjCwBgaJpZM4DCjLh
.

También noté que mencionaste que sqlalchemy podría hacer núcleo en su lugar. A menos que algo
ha cambiado mucho, solo se usa sqlalchemy core en cualquier caso, no orm. Si usted
quiere acelerar más que usar el núcleo, tiene que ir al nivel inferior, db
optimización específica

El 7 de marzo de 2017 a las 00:53, "Andrei Sura" [email protected] escribió:

@jorisvandenbossche https://github.com/jorisvandenbossche El documento
Enlacé las menciones anteriores "Alternativamente, SQLAlchemy ORM ofrece el Bulk
Conjunto de métodos de operaciones, que proporciona enlaces a las subsecciones del
unidad de proceso de trabajo para emitir INSERTAR y ACTUALIZAR de nivel central
construcciones con un pequeño grado de automatización basada en ORM".

Lo que sugiero es implementar una versión específica de sqlserver para
"to_sql", que bajo el capó utiliza el núcleo de SQLAlchemy para acelerar.


Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/pandas-dev/pandas/issues/8953#issuecomment-284437587 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AAtYVDXKLuTlsh9ycpMQvU5C0hs_RxuYks5rjCwBgaJpZM4DCjLh
.

¿Se está arreglando/atendiendo esto? A partir de ahora, insertar marcos de datos de pandas en una base de datos SQL es extremadamente lento a menos que sea un marco de datos de juguete. ¿Vamos a decidir una solución y empujarla hacia adelante?

@dfernan Como se mencionó anteriormente, es posible que desee ver el odo . El uso de un archivo CSV intermediario siempre será mucho más rápido que pasar por sqlalchemy, sin importar qué tipo de mejoras ocurran aquí...

@ostrokach , no estoy convencido de que el comportamiento de odo sea lo que quiere el usuario típico de Pandas. Las inserciones de varias filas sobre ODBC probablemente sean lo suficientemente rápidas para la mayoría de los analistas.

Para hablar por mí mismo, acabo de pasar unas horas cambiando del parche de mono de arriba a odo. El tiempo de ejecución de los pandas simples fue de más de 10 horas, RBAR. El parche mono se ejecuta en 2 en el mismo conjunto de datos.
La ruta odo/CSV fue más rápida, como se esperaba, pero no lo suficiente como para que valiera la pena el esfuerzo. Jugué con los problemas de conversión de CSV que no me importaban mucho, todo en nombre de evitar el parche de mono. Estoy importando filas 250K de ~ 10 mysql y PG DB en un área común en Postgres, para el análisis NLP.

Estoy íntimamente familiarizado con los enfoques de carga masiva que defiende odo. Los he usado durante años en los que estoy empezando con datos CSV. Hay limitaciones clave para ellos:

  1. Para el caso df->CSV->Postgres, se necesita acceso de shell y un paso scp para obtener el CSV en el host PG. Parece que @mangecoeur ha solucionado esto con una transmisión a STDIN.
  2. Para mi propósito (250 000 filas de comentarios, con muchos casos especiales en el contenido del texto), me costó obtener los parámetros CSV correctos. No quería ganancias de rendimiento lo suficientemente malas como para seguir invirtiendo en esto.

Vuelvo al parche para poder continuar con el trabajo de análisis.

Estoy de acuerdo con @jorisvandenbossche , @maxgrenderjones. Una opción (no predeterminada) para elegir esto sería inmensamente útil. El punto de @artemyk sobre dialect.supports_multivalues_insert podría incluso hacer que esto sea un valor predeterminado razonable.

Me complace enviar un PR si eso hace que esto avance.

solo para agregar mi experiencia con odo, no funcionó para las inserciones masivas de MS Sql debido a un problema conocido con la codificación. imho m-row insert es una buena solución práctica para la mayoría de las personas.

¡@markschwarz una opción para permitir que esto funcione más rápido sería muy bienvenida!

Al rastrear las consultas usando sqlite, parece que estoy ingresando inserciones múltiples cuando uso 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'))

(sin el parche de mono, eso es)

Curiosamente, con el parche de mono, se rompe cuando le doy un tamaño de trozo de 10 ^ 5, pero no 10 ^ 3. El error es "demasiadas variables sql" en sqlite.

@makmanalp , todavía no he rastreado en PG para verificar ese comportamiento, pero casi siempre configuro el tamaño de fragmento en la inserción. En mi ejemplo anterior, lo configuré aleatoriamente en 5 valores entre 200-5000. No vi diferencias drásticas en el tiempo transcurrido entre esas opciones, sin el parche de mono. Con el parche, el tiempo transcurrido se redujo ~80 %.

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

¿Sigue funcionando este parche de mono? Lo probé en MS SQL Server pero no vi una mejora. Además, lanza una excepción:

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

@hangyao Creo que el parche es específico de la implementación, una de esas cosas que python DBAPI deja al controlador DBAPI sobre cómo manejar. Así que podría ser más rápido o no. RE: el error de sintaxis, no estoy seguro de eso.

Estoy pensando en agregar la función a continuación en el archivo /io/sql.py en la línea 507 dentro de la función _engine_builder dentro de un nuevo cierre IF en la línea 521 haciendo que el 'nuevo' _engine_builder sea el siguiente retazo. Lo probé brevemente en mi entorno y funciona muy bien para bases de datos MSSQL, logrando aceleraciones >100x. Todavía no lo he probado en otras bases de datos.

La cuestión de negarme a hacer una PR es que creo que es más difícil hacerlo limpio y seguro que simplemente insertarlo como se muestra a continuación, esta puede no ser siempre la especificación deseada y agregar un interruptor booleano, que activa o desactiva esta configuración. , (por ejemplo, fast_executemany=True ) en to_sql parecía un esfuerzo demasiado grande para hacerlo sin preguntar, creo.

Entonces mis preguntas son:

  • ¿Funciona la siguiente función y también aumenta la velocidad de INSERCIÓN para PostgreSQL?

  • ¿El evento pandas quiere este fragmento en su fuente? En ese caso:

  • ¿Se desea agregar esta función a la funcionalidad predeterminada sql.py o hay un lugar mejor para agregarla?

Me encanta escuchar algunos comentarios.

Fuente de la respuesta: 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 Recientemente se ha fusionado un PR relacionado con esto: https://github.com/pandas-dev/pandas/pull/19664. Todavía no miré en detalle tu publicación, y ciertamente no es exactamente lo mismo (usa el atributo supports_multivalues_insert del motor sqlalchemy), pero solo para asegurarme de que lo sepas en caso de que esto ya ayude así como.

¡Esas son buenas noticias! No he investigado las relaciones públicas, pero las compararé este fin de semana y volveré con los resultados. Gracias por el aviso.

Solo estaba probando 0.23.0 RC2 (en postgresql) y en lugar de tener un aumento de rendimiento, mi secuencia de comandos se volvió significativamente más lenta. La consulta de base de datos se volvió mucho más rápida, pero al medir el tiempo para to_sql() , en realidad se volvió hasta 1,5 veces más lenta (como de 7 a 11 segundos)...

No estoy seguro de que la desaceleración provenga de este PR, ya que acabo de probar el RC.

¿Alguien más ha experimentado el mismo problema?

@ schettino72 ¿Cuántos datos estabas insertando?

Alrededor de 30K filas con 10 columnas. Pero realmente casi todo lo que intento es más lento (SQL es más rápido pero en general más lento). Está creando una declaración SQL enorme donde hay una interpolación de valor para CADA valor. Algo como

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

Encontré d6tstack mucho más simple de usar, es d6tstack.utils.pd_to_psql(df, cfg_uri_psql, 'benchmark', if_exists='replace') de una sola línea y es mucho más rápido que df.to_sql() . Soporta postgres y mysql. Consulte https://github.com/d6t/d6tstack/blob/master/examples-sql.ipynb

He estado usando la solución 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

desde hace algún tiempo, pero ahora estoy recibiendo un error:

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

¿Alguien más está recibiendo esto? Estoy en Python 3.6.5 (Anaconda) y pandas==0.23.0

se esta arreglando esto? Actualmente, df.to_sql es extremadamente lento y no se puede usar para muchos casos de uso práctico. El proyecto Odo parece haber sido abandonado ya.
Tengo los siguientes casos de uso en series de tiempo financieras donde df.to_sql prácticamente no se puede usar:
1) copiar datos csv históricos a la base de datos de postgres: no se puede usar df.to_sql y tuvo que usar un código personalizado en torno a la funcionalidad copy_from de psycopg2
2) transmisión de datos (que vienen en un lote de ~ 500-3000 filas por segundo) para ser volcados a la base de datos de postgres; nuevamente, el rendimiento de df.to_sql es bastante decepcionante, ya que lleva demasiado tiempo insertar estos lotes naturales de datos en postgres.
¡El único lugar donde encuentro útil df.to_sql ahora es para crear tablas automáticamente! - que no es el caso de uso para el que fue diseñado.
No estoy seguro de si otras personas también comparten la misma preocupación, pero este problema necesita atención para que las interfaces de "marcos de datos a base de datos" funcionen sin problemas.
Esperar.

Oye, recibo este error cuando intento realizar una inserción múltiple en una base de datos SQLite:

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

y me sale este error:

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 qué está pasando esto? Estoy usando Python 3.7.3 (Anaconda), pandas 0.24.2 y sqlite3 2.6.0.

¡Muchas gracias por adelantado!

@jconstanzo , ¿puedes abrir esto como un problema nuevo?
Y si es posible, ¿puede intentar proporcionar un ejemplo reproducible? (por ejemplo, un pequeño marco de datos de ejemplo que puede mostrar el problema)

@jconstanzo Tener el mismo problema aquí. Usar method='multi' (en mi caso, en combinación con chunksize ) parece desencadenar este error cuando intenta insertarlo en una base de datos SQLite.

Desafortunadamente, no puedo proporcionar un marco de datos de ejemplo porque mi conjunto de datos es enorme, esa es la razón por la que estoy usando method y chunksize en primer lugar.

Perdón por el retraso. Acabo de abrir un problema para este problema: https://github.com/pandas-dev/pandas/issues/29921

¿Fue útil esta página
0 / 5 - 0 calificaciones