Inserción masiva en Java utilizando la actualización por lotes de sentencias preparadas

Estoy intentando completar un conjunto de resultados en Java con aproximadamente 50,000 filas de 10 columnas y luego insertarlas en otra tabla usando el método batchExecute de PreparedStatement .

Para hacer el proceso más rápido investigué y descubrí que al leer datos en resultSet, fetchSize juega un papel importante.

Tener un fetchSize muy bajo puede resultar en demasiados viajes al servidor y un fetchSize muy alto puede bloquear los recursos de la red, así que experimenté un poco y configuré un tamaño óptimo que se adapta a mi infraestructura.

Estoy leyendo este conjunto de resultados y creando instrucciones de inserción para insertar en otra tabla de una base de datos diferente.

Algo así (solo una muestra, código no real):

 for (i=0 ; i<=50000 ; i++) { statement.setString(1, "a@a.com"); statement.setLong(2, 1); statement.addBatch(); } statement.executeBatch(); 
  • ¿El método executeBatch intentará enviar todos los datos a la vez?
  • ¿Hay alguna manera de definir el tamaño del lote?
  • ¿Hay alguna forma mejor de acelerar el proceso de inserción masiva?

Al actualizar a granel (50,000 filas 10 cols), ¿es mejor usar un ResultSet o un PreparedStaement actualizable con la ejecución por lotes?

Voy a dirigir sus preguntas a su vez.

  • ¿El método executeBatch intentará enviar todos los datos a la vez?

Esto puede variar con cada controlador JDBC, pero los pocos que he estudiado iterarán sobre cada entrada de lote y enviarán los argumentos junto con el identificador de sentencia preparado cada vez a la base de datos para su ejecución. Es decir, en su ejemplo anterior, habría 50,000 ejecuciones de la statement preparada con 50,000 pares de argumentos, pero estos 50,000 pasos se pueden hacer en un “bucle interno” de nivel inferior, que es donde entran los ahorros de tiempo. Como En lugar de estirar la analogía, es como abandonar el “modo de usuario” en el “modo kernel” y ejecutar todo el bucle de ejecución allí. Usted ahorra el costo de entrar y salir de ese modo de nivel inferior para cada entrada de lote.

  • ¿Hay alguna manera de definir el tamaño del lote?

Aquí lo ha definido implícitamente al presionar 50,000 conjuntos de argumentos antes de ejecutar el lote a través de Statement#executeBatch() . Un tamaño de lote de uno es igual de válido.

  • ¿Hay alguna forma mejor de acelerar el proceso de inserción masiva?

Considere abrir una transacción explícitamente antes de la inserción del lote y luego confirmarla. No permita que la base de datos o el controlador JDBC impongan un límite de transacción alrededor de cada paso de inserción en el lote. Puede controlar la capa JDBC con el método Connection#setAutoCommit(boolean) . Primero saque la conexión del modo autocompromiso , luego rellene los lotes, inicie una transacción, ejecute el lote y luego realice la transacción mediante Connection#commit() .

Este consejo asume que sus inserciones no contendrán con escritores concurrentes, y asume que estos límites de transacción le darán valores lo suficientemente consistentes leídos de sus tablas fuente para usar en las inserciones. Si ese no es el caso, favorezca la corrección sobre la velocidad.

  • ¿Es mejor utilizar un ResultSet o un PreparedStatement actualizable con la ejecución por lotes?

No hay nada mejor que probar con el controlador JDBC de su elección, pero espero que lo último: PreparedStatement y Statement#executeBatch() salgan ganando aquí. El identificador de statement puede tener una lista o matriz asociada de “argumentos por lotes”, siendo cada entrada el conjunto de argumentos proporcionado entre llamadas a Statement#executeBatch() y Statement#addBatch() (o Statement#clearBatch() ). La lista crecerá con cada llamada a addBatch() y no se executeBatch() hasta que llame a executeBatch() . Por lo tanto, la instancia de Statement realmente está actuando como un búfer de argumento; está intercambiando memoria por conveniencia (utilizando la instancia de instrucción en lugar de su propio búfer de conjunto de argumentos externo).

Nuevamente, debe considerar estas respuestas generales y especulativas siempre que no estemos discutiendo un controlador JDBC específico . Cada controlador varía en sofisticación, y cada uno variará en qué optimizaciones persigue.

El lote se realizará en “todo a la vez”: eso es lo que le pediste que hiciera.

50,000 parece un poco grande para intentar en una llamada. Lo dividiría en trozos más pequeños de 1,000, así:

 final int BATCH_SIZE = 1000; for (int i = 0; i < DATA_SIZE; i++) { statement.setString(1, "a@a.com"); statement.setLong(2, 1); statement.addBatch(); if (i % BATCH_SIZE == BATCH_SIZE - 1) statement.executeBatch(); } if (DATA_SIZE % BATCH_SIZE != 0) statement.executeBatch(); 

50,000 filas no deberían tomar más de unos pocos segundos.

Si solo se insertan los datos de una o más tablas en el DB en esta tabla y no hay intervención (alteraciones en el conjunto de resultados) , entonces se llama a statement.executeUpdate(SQL) para realizar la statement INSERT-SELECT , esto es más rápido ya que no hay gastos generales. No hay datos fuera del DB y toda la operación está en el DB, no en la aplicación.

La actualización no registrada a granel no le proporcionará el rendimiento mejorado que desea de la manera en que lo está haciendo. Mira esto