2010-09-10 22 views
42

Tengo un problema con el procesamiento de todas las filas de la base de datos (PostgreSQL). Me aparece un error: org.postgresql.util.PSQLException: Ran out of memory retrieving query results. Creo que necesito leer todas las filas en pequeños fragmentos, pero no funciona: solo lee 100 filas (código a continuación). ¿Como hacer eso?¿Cómo leer todas las filas de la tabla grande?

int i = 0;  
    Statement s = connection.createStatement(); 
    s.setMaxRows(100); // bacause of: org.postgresql.util.PSQLException: Ran out of memory retrieving query results. 
    ResultSet rs = s.executeQuery("select * from " + tabName);  
    for (;;) { 
     while (rs.next()) { 
      i++; 
      // do something... 
     } 
     if ((s.getMoreResults() == false) && (s.getUpdateCount() == -1)) { 
      break; 
     }   
    } 

Respuesta

1

Creo que su pregunta es similar a este tema: JDBC Pagination que contiene soluciones para sus necesidades.

En particular, para PostgreSQL, puede utilizar el límite y OFFSET palabras clave en su solicitud: http://www.petefreitag.com/item/451.cfm

PS: En el código de Java, sugiero que uses PreparedStatement en lugar de simples declaraciones: http://download.oracle.com/javase/tutorial/jdbc/basics/prepared.html

+2

Simplemente use Spring, prácticamente no necesita codificar contra las clases JDK - http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/jdbc.html –

+0

LIMIT y DESPLAZAMIENTO no escala bien para resultados muy grandes: \ – rogerdpack

0

Lo hice como a continuación. No es la mejor forma en que pienso, pero funciona :)

Connection c = DriverManager.getConnection("jdbc:postgresql://...."); 
    PreparedStatement s = c.prepareStatement("select * from " + tabName + " where id > ? order by id"); 
    s.setMaxRows(100); 
    int lastId = 0; 
    for (;;) { 
     s.setInt(1, lastId); 
     ResultSet rs = s.executeQuery(); 

     int lastIdBefore = lastId; 
     while (rs.next()) { 
      lastId = Integer.parseInt(rs.getObject(1).toString()); 
      // ... 
     } 

     if (lastIdBefore == lastId) { 
      break; 
     } 
    } 
55

La versión corta es, llamar stmt.setFetchSize(50); y conn.setAutoCommitMode(false); para evitar la lectura de toda la ResultSet en la memoria.

Aquí es lo que dice la documentación:

Obtención de resultados basados ​​en un cursor

Por defecto, el conductor recoge todos los resultados de la consulta a la vez. Esto puede ser inconveniente para grandes conjuntos de datos, por lo que el controlador JDBC proporciona un medio para basar un ResultSet en un cursor de base de datos y solo recuperar un pequeño número de filas.

Se almacena en caché un pequeño número de filas en el lado del cliente de la conexión y cuando se agota se recupera el siguiente bloque de filas reposicionando el cursor.

Nota: los conjuntos de resultados basados ​​

  • cursor no puede ser utilizan en todas las situaciones. Existe un número de restricciones de que hará que hace que el controlador vuelva a caer silenciosamente a obteniendo todo el ResultSet a la vez.

  • La conexión al servidor debe ser utilizando el protocolo V3. Este es el valor predeterminado para (y sólo se admite por) las versiones de servidor 7.4 y Más tarde.-

  • la conexión no debe estar en modo de confirmación automática. El back-end cierra cursores al final de las transacciones, por lo que en el modo de confirmación automática el backend habrá cerrado el cursor antes cualquier cosa puede ser traidos del ella.-

  • la sentencia debe ser creado con un tipo de conjunto de resultados ResultSet.TYPE_FORWARD_ONLY.Este es el valor por defecto , por lo que no hay código tendrá que ser reescrito para aprovechar esto, pero también significa que no se puede desplazar hacia atrás o de lo contrario salto alrededor en el ResultSet.-

  • La consulta dada debe ser una sola declaración, no múltiples declaraciones unidas con punto y coma.

Ejemplo 5.2. Establecer el tamaño de búsqueda para activar y desactivar los cursores.

Cambiar el código al modo de cursor es tan simple como establecer el tamaño de búsqueda del Statement al tamaño apropiado. Volver a establecer el tamaño de recuperación en 0 hará que todas las filas se guarden en caché (el comportamiento predeterminado).

// make sure autocommit is off 
conn.setAutoCommit(false); 
Statement st = conn.createStatement(); 

// Turn use of the cursor on. 
st.setFetchSize(50); 
ResultSet rs = st.executeQuery("SELECT * FROM mytable"); 
while (rs.next()) { 
    System.out.print("a row was returned."); 
} 
rs.close(); 

// Turn the cursor off. 
st.setFetchSize(0); 
rs = st.executeQuery("SELECT * FROM mytable"); 
while (rs.next()) { 
    System.out.print("many rows were returned."); 
} 
rs.close(); 

// Close the statement. 
st.close(); 

+0

¿Hay alguna desventaja en esto? ¿Debería habilitarlo para todas las consultas (de la redacción en el documento suena superior en todos los casos, si estás leyendo tablas grandes es mejor y si estás leyendo tablas pequeñas no importa) –

0

A no sea que en mi caso el problema fue en el cliente que intenta recoger los resultados.

Quería obtener un .csv con TODOS los resultados.

he encontrado la solución mediante el uso de

psql -U postgres -d dbname -c "COPY (SELECT * FROM T) TO STDOUT WITH DELIMITER ','" 

(donde nombre_bdd el nombre de la base de datos ...) y volver a dirigir a un archivo.

0

Así que resulta que el problema es que, de manera predeterminada, Postgres se inicia en modo "autocommit", y también necesita/utiliza cursores para poder "buscar" datos (por ej .: leer los primeros 10K resultados, luego el siguiente, luego el siguiente), sin embargo, los cursores solo pueden existir dentro de una transacción. Por lo tanto, el valor predeterminado es leer todas las filas, siempre, en la RAM, y luego permitir que su programa comience a procesar "la primera fila de resultados, luego la segunda" después de que haya llegado, por dos razones, no está en una transacción (por lo que los cursores no funciona), y tampoco se ha establecido un tamaño de búsqueda.

Entonces, ¿cómo la herramienta de línea de comandos psql logra la respuesta por lotes (su posición FETCH_COUNT) para las consultas, es de "envolver" sus consultas de selección dentro de una transacción a corto plazo (si la operación aún no está abierto), de modo que los cursores puede trabajar. Se puede hacer algo así también con JDBC:

static void readLargeQueryInChunksJdbcWay(Connection conn, String originalQuery, int fetchCount, ConsumerWithException<ResultSet, SQLException> consumer) throws SQLException { 
    boolean originalAutoCommit = conn.getAutoCommit(); 
    if (originalAutoCommit) { 
     conn.setAutoCommit(false); // start temp transaction 
    } 
    try (Statement statement = conn.createStatement()) { 
     statement.setFetchSize(fetchCount); 
     ResultSet rs = statement.executeQuery(originalQuery); 
     while (rs.next()) { 
     consumer.accept(rs); // or just do you work here 
     } 
    } finally { 
     if (originalAutoCommit) { 
     conn.setAutoCommit(true); // reset it, also ends (commits) temp transaction 
     } 
    } 
    } 
    @FunctionalInterface 
    public interface ConsumerWithException<T, E extends Exception> { 
    void accept(T t) throws E; 
    } 

Esto da la ventaja de requerir menos memoria RAM, y, en mis resultados, parecía correr más rápido en general, incluso si no es necesario para salvar la memoria RAM. Extraño. También da la ventaja de que el procesamiento de la primera fila "comienza más rápido" (ya que lo procesa una página a la vez).

Y he aquí cómo hacerlo con el "cursor de postgres en bruto", junto con la demo completa code, aunque en mis experimentos parecía que el modo JDBC, arriba, era ligeramente más rápido por cualquier razón.

Otra opción sería desactivar el modo autoCommit, en cualquier lugar, aunque todavía tiene que especificar manualmente un fetchSize para cada nuevo Statement (o puede establecer un tamaño de búsqueda predeterminado en la cadena URL).

Cuestiones relacionadas