2010-06-07 11 views
43

Necesito insertar un par de cientos de millones de registros en el mysql db. Estoy por lotes insertándolo 1 millón a la vez. Por favor vea mi código abajo. Parece ser lento. ¿Hay alguna forma de optimizarlo?Rendimiento de inserción por lotes JDBC

try { 
     // Disable auto-commit 
     connection.setAutoCommit(false); 

     // Create a prepared statement 
     String sql = "INSERT INTO mytable (xxx), VALUES(?)"; 
     PreparedStatement pstmt = connection.prepareStatement(sql); 

     Object[] vals=set.toArray(); 
     for (int i=0; i<vals.length; i++) { 
      pstmt.setString(1, vals[i].toString()); 
      pstmt.addBatch(); 
     } 

     // Execute the batch 
     int [] updateCounts = pstmt.executeBatch(); 
     System.out.append("inserted "+updateCounts.length); 
+0

Su código se corrompió un poco (y se truncó prematuramente) – Uri

+0

BTW, ¿qué controlador está utilizando? ¿Un JDBC general o el conector JDBC-Mysql? – Uri

+0

Estoy usando com.mysql.jdbc.Driver – user157195

Respuesta

8

puede insertar varias filas con una instrucción de inserción, haciendo unos pocos miles a la vez puede acelerar enormemente las cosas, es decir, en lugar de hacerlo, por ejemplo, 3 inserciones del formulario INSERT INTO tbl_name (a,b,c) VALUES(1,2,3);, lo hace INSERT INTO tbl_name (a,b,c) VALUES(1,2,3),(1,2,3),(1,2,3); (Podría ser que JDBC .addBatch() haga una optimización similar ahora, aunque el mysql addBatch solía estar indefinidamente entierro y simplemente emitir consultas individuales de todos modos, no sé si eso todavía está el caso con los controladores recientes)

Si realmente necesita velocidad, cargue sus datos de un archivo separado por comas con LOAD DATA INFILE, obtenemos aproximadamente 7-8 veces la aceleración haciéndolo frente a hacer decenas de millones de insertos.

+0

load data infile podría ser una buena alternativa, pero mi archivo de entrada necesita una limpieza, solo estoy interesado en insertar ciertas filas donde el segundo token coincide con una cadena (tokens delimitados por espacios), ¿es lo suficientemente flexible para filtrar filas? – user157195

+3

No creo que se pueda filtrar, pero puede limpiar los datos usted mismo, escribir un nuevo archivo con los datos limpios y cargar ese archivo. – nos

+0

¡Mis inserciones son 10 veces más rápidas ahora! – user393274

3

Si:

  1. Es una nueva tabla, o la cantidad que se inserta es mayor que los datos ya insertados
  2. Hay índices de la tabla
  3. No es necesario otro tipo de acceso a la tabla durante la inserción

Entonces ALTER TABLE tbl_name DISABLE KEYS puede mejorar en gran medida la velocidad de sus inserciones. Cuando haya terminado, ejecute ALTER TABLE tbl_name ENABLE KEYS para comenzar a crear los índices, lo que puede llevar un tiempo, pero no tanto como hacerlo para cada inserción.

1

Puede intentar usar el objeto DDBulkLoad.

// Get a DDBulkLoad object 
DDBulkLoad bulkLoad = DDBulkLoadFactory.getInstance(connection); 
bulkLoad.setTableName(“mytable”); 
bulkLoad.load(“data.csv”); 
126

que tenían un problema de rendimiento similar con mysql y lo resolvió mediante el establecimiento de los useServerPrepStmts y los rewriteBatchedStatements propiedades en el URL de conexión.

Connection c = DriverManager.getConnection("jdbc:mysql://host:3306/db?useServerPrepStmts=false&rewriteBatchedStatements=true", "username", "password"); 
+0

¡Agradable! Estoy viendo una mejora 3x – Kimble

+4

@Kimble - ¿por qué no aceptar esta respuesta? ¡Gracias, amigo! ¡Esto funciona como magia! –

+0

¡OMG! Agregar los parámetros anteriores a mi URL de conexión aceleró las inserciones de lotes casi 30x. No estoy seguro de qué otras implicaciones tienen estas variables. ¡Pero increíble es! Gracias. – Keshav

39

Me gustaría ampliar la respuesta de Bertil, ya que he estado experimentando con los parámetros de la URL de conexión.

rewriteBatchedStatements=true es el parámetro importante. useServerPrepStmts ya es falso de forma predeterminada, e incluso cambiarlo a verdadero no hace mucha diferencia en términos de rendimiento de inserción de lote.

Ahora creo que es el momento de escribir cómo rewriteBatchedStatements=true mejora el rendimiento tan dramáticamente. Lo hace por rewriting of prepared statements for INSERT into multi-value inserts when executeBatch() (Source). Esto significa que en lugar de enviar los n instrucciones INSERT siguiente para el servidor MySQL cada vez executeBatch() se llama:

INSERT INTO X VALUES (A1,B1,C1) 
INSERT INTO X VALUES (A2,B2,C2) 
... 
INSERT INTO X VALUES (An,Bn,Cn) 

que enviaría una única instrucción INSERT:

INSERT INTO X VALUES (A1,B1,C1),(A2,B2,C2),...,(An,Bn,Cn) 

se puede observar que al alternar en el registro de mysql (por SET global general_log = 1) que registraría en un archivo cada declaración enviada al servidor de mysql.

+0

¿Funciona para db2? – Vipin

+0

@Vipin No tengo idea. – Eran

0
try { 
     // Disable auto-commit 
     connection.setAutoCommit(false); 
     int maxInsertBatch = 10000;  
     // Create a prepared statement 
     String sql = "INSERT INTO mytable (xxx), VALUES(?)"; 
     PreparedStatement pstmt = connection.prepareStatement(sql); 

     Object[] vals=set.toArray(); 
     int count = 1; 
     for (int i=0; i<vals.length; i++) { 
      pstmt.setString(1, vals[i].toString()); 
      pstmt.addBatch(); 
      if(count%maxInsertBatch == 0){ 
       pstmt.executeBatch(); 
      } 
      count++; 
     } 

     // Execute the batch 
     pstmt.executeBatch(); 
     System.out.append("inserted "+count); 
+0

en lugar de downvoting podría haber comentarios al respecto, por qué puede mejorar el rendimiento o no al ejecutar varios lotes intermedios y no todos a la vez ... – benez

Cuestiones relacionadas