2012-10-08 12 views
8

Tengo una tabla de MySQL que tiene este aspecto:rellenar un MySQL con una gran serie de filas rápidamente

MySQL Table: status

El SQL para crear la estructura es:

CREATE TABLE `status` (
`id` INT(11) NOT NULL, 
`responseCode` INT(3) NOT NULL DEFAULT '503', 
`lastUpdate` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 
PRIMARY KEY (`id`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 

almacena una único id, responseCode y lastUpdate. El responseCode es un código de respuesta HTTP Request: 404, 500, 503, 200, etc.

tengo una URL para corresponder a cada id para el que hago una petición HTTP y registro en esta tabla el momento que hice la solicitud y la respuesta recibida.

El guión hace que esta consulta en la tabla status:

SELECT id FROM status WHERE lastUpdate < 'XXXX' OR 
(responseCode != 200 AND responseCode != 404) 
ORDER BY id DESC LIMIT 100 

Dónde XXXX habría una fecha en que decido que nada más antigua que la fecha debe ser refrescado independientemente del código de respuesta. Además, deseo volver a intentar la solicitud HTTP si no obtuve un 200 o 404 independientemente de la fecha de la última lastUpdate. I LIMIT a 100 porque solo corro 100 a la vez, y luego lo tengo dormir por un tiempo y hago otros 100 más adelante, y así sucesivamente.

De todas formas, todo eso está muy bien, pero lo que yo quiero hacer es rellenar la tabla antes de tiempo con decir una serie como esta:

(1, 503, NOW()), (2, 503, NOW()), (3, 503, NOW()) ... (100000, 503, NOW()) 

Aviso, sólo el ID está incrementando, pero no necesariamente comenzar en 1 para mis necesidades Quiero que la tabla se rellene previamente de esta manera, porque luego la consulta de arriba puede seguir agarrando id para los que necesitamos volver a intentar, y me gustaría no tener que insertar nada más en la tabla status como id 's son finitos y no cambiarán (pero hay muchos de ellos).

He intentado utilizar Java, (aunque PHP, C#, o cualquier otra cosa es el mismo concepto y no me importa que el lenguaje que uso aquí):

PreparedStatement st = conn.prepareStatement("INSERT INTO status VALUES (?,default,default)"); 

for(int i = 1; i <= 100000; i++) { 
    st.setInt(1,i); 
    st.addBatch(); 
} 

System.out.println("Running batch..."); 
st.executeBatch(); 
System.out.println("Batch done!"); 

Esto inicia los insertos, pero el El problema es que se necesita una cantidad extraordinaria de tiempo para llenar la tabla (no tengo una hora exacta, pero estuvo funcionando durante horas). Entonces, mi pregunta se reduce a: ¿hay una forma fácil y eficiente de llenar una tabla MySQL con una cantidad masiva de filas como esta?

+0

añadió una solución SQL puro a mi respuesta, que me haga saber si encuentras algo más rápido. – xception

Respuesta

11

Generalmente hablando, puede utilizar uno o más de los siguientes:

  • iniciar una transacción, no insertos, comprometerse
  • paquete de múltiples valores en una sola inserción en consulta
  • gota cualquier co nstraints antes de hacer inserción y restablecer las restricciones después de la inserción de masas (excepto la tecla posiblemente primaria, no muy seguro de ello, aunque)
  • Uso insert into ... select si es conveniente

El primero (el uso de transacciones) es más probable que ayude, pero No estoy seguro de si funciona en las tablas myisam, con innodb hace un muy buen trabajo. Solo las uso cuando estoy obligado a usar mysql, prefiero postgresql.

En su caso específico, la inserción de 100000 filas de datos, se puede hacer lo siguiente:

INSERT INTO status(id, responseCode, lastUpdate) SELECT @row := @row + 1 as row, 503, NOW() FROM 
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) t, 
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) t2, 
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) t3, 
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) t4, 
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) t5, 
(SELECT @row:=0) t6; 

probado esto en mi máquina, tiene:

Query OK, 100000 rows affected (0.70 sec) 
Records: 100000 Duplicates: 0 Warnings: 0 

estoy bastante seguro de que puede Se obtiene mucho más rápido que eso en 100000 filas.

+2

Si está ejecutando muchas instrucciones de inserción, al agruparlas en transacciones impide que el archivo db escriba en el disco después de cada una, se asegura de que se comprometan en el disco de una vez al final de la transacción . –

+1

Esto parece bastante rápido, ¿puedes explicar brevemente qué está pasando en esa consulta? – user17753

+0

Creo números consecutivos, uniendo 5 tablas que contienen de 0 a 9 y luego selecciono Número, constante, constante ... que es muy rápido ... luego inserto todas las 100000 entradas en una sola transacción ya que es una consulta única. – xception

1

Está creando una instrucción GRANDE por lotes que se realizará. Intente dividirlo en paquetes más pequeños utilizando, por ej. call executeBatch() cada 1000 incrementos de i (usando mod (i) yaddayadda) dentro del bucle. Eso debería acelerar el proceso:

for(int i = 1; i <= 100000; i++) { 
    st.setInt(1,i); 
    st.addBatch(); 
    if (mod(i,1000)=0) { 
     st.executeBatch(); 
    } 
} 
+0

Me he dado cuenta de que la ejecución del lote (como en mi pregunta) todavía llena la tabla de forma activa (por ejemplo, puedo ver el relleno de db) igual que su fragmento aquí. Sin embargo, no experimento ninguna diferencia importante en el rendimiento de los insertos. – user17753

8

Cómo acerca de la configuración AUTO_INCREMENT en la clave principal.

Luego insertando las primeras cien (o miles) filas de la manera que quieras (tu ejemplo o el ejemplo que DocJones te dio).

Luego, utilizando

INSERT INTO table SELECT NULL, '503', NOW() FROM table; 

...repetidamente algunas veces Esto debería hacer que la mesa tenga el doble de tamaño cada vez.

El NULL en la primera ranura del SELECT asegura que el AUTO_INCREMENT entra en acción y aumenta id.

Si quieres hacer crecer la mesa, incluso faser que puede hacer

INSERT INTO table SELECT NULL, '503', NOW() FROM table AS t1 CROSS JOIN table t2; 

... repetidamente un par de veces que haría que el aumento de mesa de tamaño con potencias de dos del tamaño anterior + tamaño anterior (100^2 + 100).

Esto también le permite personalizar los valores insertados por ejemplo, si desea crear "al azar" responseCodes se puede usar algo como CONCAT(ROUND(1+RAND()*4), '0', ROUND(RAND()*5)) que dará respuesta códigos que van desde 100 a 505.

+0

¡Excelente solución también! – DocJones

+0

Creo que esta idea es muy interesante. Voy a probar esto. – user17753

+1

Ten cuidado con 'CROSS JOIN' puedes ingresar 10 valores manualmente y ejecutar 'CROSS JOIN' y obtener 10 + 10^2 = 110, luego repetirás 'CROSS JOIN' y poof tendrás 110 + 110^2 = 12,210, en la tercera repetición ya estás en 149,096,310 - ciento cuarenta y nueve ** millones ** de entradas, que se comerán en algún disco-io - y tomarás un tiempo para escribir. –

2

solución PHP para cargarlos en lotes de 100:

for ($i = 0; $i < 100000; $i+=100) { 
    $vals = implode(', ', 
        array_map(function($j) { return "($j, default, default)";}, 
          range($i, $i+100))); 
    mysqli_query($dbh, 'insert into status values ' . $vals) or die mysqli_error($dbh); 
} 
Cuestiones relacionadas