2010-03-15 11 views
38

Estoy desarrollando una aplicación de primavera que utiliza grandes tablas de MySQL. Al cargar tablas grandes, obtengo un OutOfMemoryException, ya que el controlador intenta cargar toda la tabla en la memoria de la aplicación.Transmisión de grandes conjuntos de resultados con MySQL

He intentado utilizar

statement.setFetchSize(Integer.MIN_VALUE); 

pero entonces cada conjunto de resultados abro cuelga en close(); buscando en línea me encontré con que esto sucede porque se trata de cargar las filas no leídos antes de cerrar el conjunto de resultados, pero ese no es el caso, ya que hago esto:

ResultSet existingRecords = getTableData(tablename); 
try { 
    while (existingRecords.next()) { 
     // ... 
    } 
} finally { 
    existingRecords.close(); // this line is hanging, and there was no exception in the try clause 
} 

Los bloqueos ocurren por pequeñas mesas (3 filas), así, y si no cierro el RecordSet (que sucedió en un método), entonces se bloquea connection.close().


Seguimiento de la pila de la caída:

SocketInputStream.socketRead0 (FileDescriptor, byte [], int, int, int) de la línea: no disponible [método nativo]
SocketInputStream.read (byte [], int, int) línea: 129
ReadAheadInputStream.fill (int) línea: 113
ReadAheadInputStream.readFromUnderlyingStreamIfNecessary (byte [], int, int) línea: 160
ReadAheadInputStream.read (byte [], int, int) línea: 188
MysqlIO.readFully (InputStream, byte [], int, int) línea: 2428 MysqlIO.reuseAndReadPacket (Buffer, int) línea: 2882
MysqlIO.reuseAndReadPacket línea (Buffer): 2871
MysqlIO.checkErrorPacket (int) línea: 3414
MysqlIO.checkErrorPacket() de la línea: 910
MysqlIO.nextRow (Field [], int, boolean, int, boolean, boolean, boolean, Buffer) línea: 1405
RowDataDynamic.nextRecord línea(): 413
Línea RowDataDynamic.next(): 392 Línea RowDataDynamic.close(): 170
JDBC4ResultSet (ResultS etImpl) .realClose (boolean) línea: 7473 JDBC4ResultSet (ResultSetImpl) .close() de la línea: 881 DelegatingResultSet.close) línea (: 152
DelegatingResultSet.close() de la línea: 152
DelegatingPreparedStatement (DelegatingStatement) .close () línea: 163
(Esta es mi clase) Database.Close() línea: 84

+0

No creo que el establecimiento MIN_VALUE hace algo útil. ¿Cómo devolverías como máximo -2^31 registros? –

+7

Es algo misterioso. MySQL no admite ese setFetchSize tradicional, por lo que se ignora cualquier valor, excepto Integer.MIN_VALUE, que causa la transmisión correcta. – configurator

+2

¿Dónde está colgando la instrucción close (stack trace)? ¿Qué versión de MySQL está usando y qué controlador JDBC? – jarnbjo

Respuesta

12

No cierre su ResultSet s dos veces.

Al parecer, cuando se cierra una Statement Tiene por objeto eliminar la ResultSet correspondiente, como se puede ver en estas dos líneas desde el seguimiento de la pila:

DelegatingResultSet.close() Línea: 152
DelegatingPreparedStatement (DelegatingStatement) .close() línea: 163

que había pensado la caída estaba en ResultSet.close() pero era en realidad en la que se pide Statement.close()ResultSet.close(). Como el ResultSet ya estaba cerrado, simplemente colgó.

Hemos reemplazado todo ResultSet.close() con results.getStatement().close() y quitó todos Statement.close() s, y el problema queda resuelta.

+1

Me alegra que lo hayas solucionado.Sin embargo, la expresión normal de JDBC es cerrar los recursos en ** orden invertido ** a medida que los adquirió. Algunos controladores JDBC (incluido MySQL uno) efectivamente implícitamente intentan cerrar cualquier recurso "hijo" abierto. Por lo tanto, después de abrir Connection, Statement y ResultSet en este orden, debe cerrar ResultSet, Statement y Connection en este orden. No debe dejar la instrucción abierta, puede perder recursos, especialmente cuando usa la agrupación de conexiones en la que la conexión real no se cerrará directamente. Por cierto: me parece extraño que haya arrojado OOME en lugar de SQLException. – BalusC

+0

El OOME era cuando no se transmitía; cuando la transmisión se acaba de colgar de cerca. La forma "correcta" de hacer esto es lo primero que probamos, y el que no funcionó; cerramos ResultSet, luego nos colgamos cerrando la declaración (que intentó volver a cerrar ResultSet). Ahora solo cerramos la declaración y ojalá no tengamos pérdidas de memoria. Gracias por sus consejos – configurator

+0

¿Pero por qué cuelga? También me encuentro con este problema. Pasaron unos 30 segundos para cerrar. – wener

52

Sólo el establecimiento de la zona de alcance el tamaño no es el enfoque correcto. El javadoc of Statement#setFetchSize() ya se indica lo siguiente:

da al conductor JDBC un pista en cuanto al número de filas que se debe recuperar de la base de datos

El conductor es realmente libre de aplicar o ignora la sugerencia. Algunos controladores lo ignoran, algunos controladores lo aplican directamente, algunos controladores necesitan más parámetros. El controlador JDBC de MySQL cae en la última categoría.Si marca la MySQL JDBC driver documentation, verá la siguiente información (desplazarse hacia abajo hasta aproximadamente 2/3 de cabecera conjunto de resultados):

Para activar esta funcionalidad, es necesario crear una instancia Declaración de la siguiente forma:

stmt = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); 
stmt.setFetchSize(Integer.MIN_VALUE); 

Por favor, lea toda la sección del documento, que describe las advertencias de este enfoque también. Aquí hay una cita relevante:

Hay algunas advertencias con este enfoque. Tendrá que leer todas las filas del conjunto de resultados (o cerrarlo) antes de poder emitir otras consultas en la conexión, o se lanzará una excepción.

(...)

Si la instrucción está dentro de alcance de una transacción, a continuación, los bloqueos se liberan cuando se complete la transacción (lo que implica que la declaración tiene que completar primero). Al igual que con la mayoría de las otras bases de datos, las declaraciones no se completan hasta que se leen todos los resultados pendientes en la declaración o cuando se cierra el conjunto de resultados activos para la declaración.

Si esto no soluciona el OutOfMemoryError (no Exception), entonces es probable que el problema que usted está almacenando todos los datos en la memoria de Java en lugar de procesarlo inmediatamente tan pronto como los datos entra en acción. Esto requeriría más cambios en su código, tal vez una reescritura completa. He respondido una pregunta similar antes del here.

+0

Esto parece extrañamente familiar; es una copia y pega de su respuesta en http://stackoverflow.com/questions/2095490/. Y es irrelevante, ya que me dice que haga lo que ya dije que hice y me causó problemas, en la pregunta misma. – configurator

+5

¿Por qué no puedo copiar las partes relevantes de mis propias palabras? Además, no dijo que creó la declaración según el documento MySQL JDBC. He visto con demasiada frecuencia que cometieron el error de establecer * solo * FetchSize. – BalusC

+0

@configurator parece que regaña a BalusC por darle una respuesta (junto con su voto negativo). +1 porque iba a responder lo mismo (sobre el 'TYPE_FORWARD_ONLY') – Bozho

4

En caso de que alguien tenga el mismo problema, lo resolví utilizando la cláusula LIMIT en mi consulta.

Este problema se informó a MySql como un error (encuéntrelo aquí http://bugs.mysql.com/bug.php?id=42929) que ahora tiene el estado "no es un error".La parte más pertinente es:

No hay manera actualmente para cerrar un conjunto de resultados "mitad de camino"

Puesto que usted tiene que leer todas las filas, se tendrá que limitar sus resultados de la consulta utilizando una cláusula como DÓNDE o LÍMITE. Alternativamente, intente lo siguiente:

ResultSet rs = ... 
while(rs.next()) { 
    ... 
    if(bailOut == true) { break; } 
} 

while(rs.next()); // This will deplete the remaining rows on the stream 

rs.close(); 

Puede que no sea lo ideal, pero al menos le permite pasar de moda.

0

Se bloquea porque incluso si deja de escuchar, la solicitud continúa. Con el fin de cerrar el conjunto de resultados y la declaración en el orden correcto, intente llamar Statement.cancel() primero:

public void close() { 
    try { 
     statement.cancel(); 
     if (resultSet != null) 
      resultSet.close(); 
    } catch (SQLException e) { 
     // ignore errors on closing 
    } finally { 
     try { 
      statement.close(); 
     } catch (SQLException e) { 
      // ignore errors on closing 
     } finally { 
      resultSet = null; 
      statement = null; 
     } 
    } 
} 
Cuestiones relacionadas