2009-05-11 58 views
11

Tengo una tabla de base de datos con un campo de cadena único y un par de campos enteros. El campo de cadena suele tener entre 10 y 100 caracteres.Cuál es la forma más eficiente de insertar miles de registros en una tabla (MySQL, Python, Django)

Una vez cada minuto más o menos tengo la siguiente situación: recibo una lista de 2-10 mil tuplas correspondientes a la estructura de registro de la tabla, p.

[("hello", 3, 4), ("cat", 5, 3), ...] 

tengo que insertar todas estas tuplas de la tabla (se supone He verificado ninguna de estas cadenas aparecen en la base de datos). Para aclarar, estoy usando InnoDB, y tengo una clave primaria autoincremental para esta tabla, la cadena no es PK.

Mi código actualmente itera a través de esta lista, para cada tupla crea un objeto de módulo de Python con los valores apropiados, y llama ".save()", algo así:

@transaction.commit_on_success 
def save_data_elements(input_list): 
    for (s, i1, i2) in input_list: 
     entry = DataElement(string=s, number1=i1, number2=i2) 
     entry.save() 

Este código es actualmente uno de los cuellos de botella de rendimiento en mi sistema, entonces estoy buscando formas de optimizarlo.

Por ejemplo, podría generar códigos SQL que contengan cada uno un comando INSERT para 100 tuplas ("codificadas" en el SQL) y ejecutarlo, pero no sé si mejorará algo.

¿Tiene alguna sugerencia para optimizar dicho proceso?

Gracias

+0

¡Buena pregunta! Entonces, ¿las mejores respuestas parecen ser la creación de un archivo de texto o la generación de una consulta SQL a través de la concatenación de cadenas? Esto es un poco insatisfactorio – JAL

Respuesta

11

es posible escribir las filas en un archivo en el formato "campo1", "campo2", .. y luego usar LOAD DATA para cargarlos

data = '\n'.join(','.join('"%s"' % field for field in row) for row in data) 
f= open('data.txt', 'w') 
f.write(data) 
f.close() 

A continuación, ejecute la siguiente:

LOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table; 

Reference

+0

Debería estar LOAD DATA LOCAL INFILE a menos que el código se esté ejecutando en el servidor de la base de datos. – staticsan

+0

Además, desactive los índices antes de cargarlos y luego, actívelos (llevará un tiempo construir el índice). No he visto si ayuda con las inserciones de Django también. – pufferfish

12

Específicamente para MySQL, la forma más rápida de cargar datos es usando LOAD DATA INFILE, por lo que si puede convertir los datos en el formato que espera, probablemente sea la forma más rápida de incluirlo en la mesa.

+0

El único problema potencial es anular el método save(). Si haces esto, tendrás que pensar dos veces acerca de tu diseño. –

+0

@ S.Lott: ¿qué quiere decir con "anular el guardado()"? ¿Quiere decir si anulo el método .save() en la clase de módulo para que haya tareas de procesamiento previo/posterior mientras se guarda el código que se perderá en el "cargar archivo de datos"? Si es así, ese no es el caso, no estoy anulando .save(). De lo contrario, elabore ... Gracias –

4

Si no lo hace LOAD DATA INFILE como algunas de las otras sugerencias mencionan, dos cosas que puede hacer para acelerar sus inserciones son:

  1. Uso prepara declaraciones - esto reduce la sobrecarga de analizar el SQL para cada inserte
  2. hacer todas sus inserciones en una sola transacción - esto requeriría el uso de un motor de base de datos que soporta transacciones (como InnoDB)
+0

@Sean: Gracias, por "declaraciones preparadas" ¿te refieres al código SQL con muchos% s elementos que simplemente "llené" al proporcionar la lista de cadenas/números? Además, eche un vistazo a mi código (en el cuerpo de la pregunta) - si lo entiendo correctamente, ya estoy usando una sola transacción con el decorador @ transaction.commit_on_success (estoy usando InnoDB) –

+0

No soy realmente seguro de lo que está sucediendo detrás de Scenese con Django: solo vengo de un trasfondo genérico de usar MySQL, así que no sé qué está haciendo con respecto a las transacciones. En cuanto a las declaraciones preparadas, parece que se trata de un detalle de implementación de sus objetos DataElement. Una declaración preparada sería: 'stmt = Prepare (sqlStatement); stmt.execute (var1, var2 ..) 'en lugar de' db.execute (sqlStatement, var1, var2 ...) '- es como compilar expresiones regulares en lugar de analizarlas cada vez. –

4

Si puede hacer una declaración hecha a mano INSERT, entonces ese es el camino que seguiría. Una sola instrucción INSERT con cláusulas de valores múltiples es mucho más rápida que muchas declaraciones individuales INSERT.

+1

@staticsan: ¿Cree que hay alguna limitación "práctica" a tal afirmación? es decir, ¿puedo enviar a la base de datos una única consulta INSERT con 10k líneas de texto? –

+0

La única limitación real es el tamaño del búfer de red. El valor predeterminado de esto fue de 1Mb durante muchos años, pero muchas personas lo elevaron al máximo de 16Mb. Las versiones más recientes de MySQL pueden admitir incluso grandes tamaños de paquetes. – staticsan

+1

Se trata más del tamaño del paquete que la cantidad de registros. A medida que construye su búfer de inserción, no agregue más si hacerlo colocará el búfer sobre el tamaño máximo de paquete mysql. Haría una evaluación comparativa y vería dónde el beneficio comienza a estabilizarse. También puede preguntarle a su servidor MySQL por su tamaño máximo de paquete: mysql> seleccionar @@ max_allowed_packet \ G: @@ max_allowed_packet: 33554432 – Will

1

Esto no está relacionado con la carga real de datos en el DB, pero ...

Si proporciona un "Los datos se están cargando ... La carga se realizará en breve" es una opción el tipo de mensaje para el usuario, luego puede ejecutar los INSERT o CARGAR DATOS de forma asíncrona en un hilo diferente.

Algo más a considerar.

+0

Lo más probable es que el servidor esté tan ocupado procesando esta entrada que no puede manejar ninguna otra solicitud . –

+0

Ya estoy procesando en un hilo separado (el usuario no está esperando que esto termine), mi problema es que a veces el sistema está demasiado ocupado, por lo que existe la posibilidad de que la cola se llene más rápido que su limpieza solo lo suficiente tiempo ... –

2

Independientemente del método de inserción, querrá usar el motor InnoDB para una concurrencia máxima de lectura/escritura. MyISAM bloqueará la tabla completa mientras dure la inserción, mientras que InnoDB (en la mayoría de los casos) solo bloqueará las filas afectadas, permitiendo que las instrucciones SELECT continúen.

+0

Gracias, agregué una aclaración de que estoy usando InnoDB –

1

No conozco los detalles exactos, pero puede usar la representación de datos de estilo json y utilizarla como accesorios o algo así. Vi algo similar en Django Video Workshop por Douglas Napoleone. Vea los videos en http://www.linux-magazine.com/online/news/django_video_workshop. y http://www.linux-magazine.com/online/features/django_reloaded_workshop_part_1. Espero que esto ayude.

Espero que pueda resolverlo. Acabo de empezar a aprender django, así que puedo apuntarlo a los recursos.

Cuestiones relacionadas