2010-10-15 21 views
12

Estoy desarrollando un programa en Python que accede a una base de datos MySQL utilizando MySQLdb. En ciertas situaciones, tengo que ejecutar un comando INSERTAR o REEMPLAZAR en muchas filas. Actualmente lo estoy haciendo así:¿Por qué es lento ejecutar en Python MySQLdb?

db.execute("REPLACE INTO " + table + " (" + ",".join(cols) + ") VALUES" + 
    ",".join(["(" + ",".join(["%s"] * len(cols)) + ")"] * len(data)), 
    [row[col] for row in data for col in cols]) 

Funciona bien, pero es un poco incómodo. Me preguntaba si podría facilitar la lectura, y descubrí el comando executemany. Cambié mi código para que se vea así:

db.executemany("REPLACE INTO " + table + " (" + ",".join(cols) + ") " + 
    "VALUES(" + ",".join(["%s"] * len(cols)) + ")", 
    [tuple(row[col] for col in cols) for row in data]) 

Aún funcionaba, pero funcionaba mucho más lento. En mis pruebas, para conjuntos de datos relativamente pequeños (alrededor de 100-200 filas), se ejecutó aproximadamente 6 veces más lento. Para grandes conjuntos de datos (alrededor de 13,000 filas, la más grande que espero manejar), se ejecutó unas 50 veces más lento. ¿Por qué está haciendo esto?

Realmente me gustaría simplificar mi código, pero no quiero la gran caída en el rendimiento. ¿Alguien sabe de alguna manera para hacerlo más rápido?

Estoy usando Python 2.7 y MySQLdb 1.2.3. Traté de jugar con la función setinputsizes, pero eso no pareció hacer nada. Miré el código fuente MySQLdb y parece que no debería hacer nada.

+0

¿cuántas filas está insertando/reemplazando? su segunda declaración crea una gran lista en la memoria antes de alimentarlo a mysql. – nosklo

+1

Estoy reemplazando hasta 13,000 filas. No creo que crear la lista sea el cuello de botella. Si creo la lista pero no la paso al cursor db, apenas toma tiempo. –

+0

(No responderá la pregunta, pero ...) 'INSERTAR ... EN ACTUALIZACIÓN DE LLAVE DUPLICADA ...' casi siempre es mejor que 'REEMPLAZAR ...'. –

Respuesta

19

Intente minicar la palabra 'valores' en su consulta; esto parece ser un error/regresión en MySQL-python 1.2.3.

La implementación de MySQL-python de executemany() coincide con la cláusula VALUES con una expresión regular y luego clona la lista de valores para cada fila de datos, por lo que termina ejecutando exactamente la misma consulta que con su primer acercamiento. Desafortunadamente, la expresión regular perdió su indicador insensible a mayúsculas y minúsculas en esa versión (posteriormente se corrigió en la troncal r622 pero nunca volvió a la rama 1.2) por lo que se degrada a iterar sobre los datos y desencadenar una consulta por fila.

+0

¡Lo intenté y funciona! Con "valores" en minúsculas, es tan rápido con ejecutar muchos como con ejecutar, o algunas veces un poco más rápido. –

+1

Tenga en cuenta que 1.2.3 regex no funciona con argumentos en ON DUPLICATE KEY UPDATE consultas (la expresión regular solo coincide con los primeros argumentos), por lo que los valores de la carcasa inferior pueden dar lugar a confusión (porque funcionan con execute()) "no todos los argumentos convertidos durante el formateo de cadenas "errors. Para evitarlo, utilice el formato VALUES() en lugar de argumentos en la parte ON DUPLICATE KEY de la consulta. –

+0

Se corrigió en [1.2.4] (https://github.com/farcepest/MySQLdb1/blob/MySQLdb-1.2.4/MySQLdb/cursors.py#L43). – saaj

1

Su primer ejemplo es una declaración única (grande) que se genera y luego se envía a la base de datos.

El segundo ejemplo es una declaración mucho más simple que inserta/reemplaza una sola fila pero se ejecuta varias veces. Cada comando se envía a la base de datos por separado, por lo que debe pagar el tiempo de respuesta de cliente a servidor y viceversa por cada fila insertada. Creo que esta latencia adicional introducida entre los comandos es la razón principal de la disminución del rendimiento del segundo ejemplo.

+0

Eso es lo que sospechaba.Pensé que tal vez la función executemany sea lo suficientemente sofisticada como para enviar los comandos todo en una consulta, pero no parece ser así. –

1

No recomiendo usar executeMany en pyodbc ni ceodbc, ambos son lentos y contienen muchos errores.

Considere el uso en su lugar execute y construya manualmente la consulta SQL usando un formato de cadena simple.

transaction = "TRANSACTION BEGIN {0} COMMIT TRANSACTION 

bulkRequest = "" 
for i in range(0, 100) 
    bulkRequest = bulkRequest + "INSERT INTO ...... {0} {1} {2}" 

ceodbc.execute(transaction.format(bulkRequest)) 

La implementación actual es muy simple, rápida y confiable.