2010-09-16 15 views
4

J'écris un programme complet d'extraction de base de données en Java. La base de données est Oracle, et c'est énorme. Certaines tables ont ~ 260 millions d'enregistrements. Le programme devrait créer un fichier par table dans un format spécifique, donc utiliser Oracle datapump etc n'est pas une option. En outre, certaines stratégies de sécurité de l'entreprise ne permettent pas d'écrire une procédure PL/SQL pour créer des fichiers sur le serveur de base de données pour cette exigence. Je dois aller avec Java et JDBC.Comment surmonter OutOfMemoryError lors de l'écriture de gros fichiers

Le problème auquel je suis confronté est que depuis que les fichiers pour certains de la table est énorme (~ 30 Go), je suis à court de mémoire presque à chaque fois, même avec un tas Java de 20 Go. Lors de la création d'un fichier lorsque la taille du fichier dépasse la taille du segment, même avec l'une des stratégies GC les plus agressives, le processus semble se bloquer. Par exemple, si la taille du fichier est supérieure à 20 Go et la taille du segment de mémoire de 20 Go, une fois que l'utilisation du tas atteint la taille maximum, elle ralentit l'écriture de 2 Mo par minute.

Je cherche un moyen de surmonter ce problème. Toute aide serait grandement appréciée.

Voici quelques détails de la configuration du système que j'ai: Java - JDK1.6.0_14

configuration système - RH Enterprise Linux (2.6.18) fonctionnant sur 4 X Intel Xeon E7450 (6 cœurs) @ 2,39 GH

RAM - 32 Go

Oracle Database 11g

fichier

wirting partie du code ci-dessous va:

private void runQuery(Connection conn, String query, String filePath, 
     String fileName) throws SQLException, Exception { 
    PreparedStatement stmt = null; 
    ResultSet rs = null; 
    try { 
     stmt = conn.prepareStatement(query, 
       ResultSet.TYPE_SCROLL_INSENSITIVE, 
       ResultSet.CONCUR_READ_ONLY); 
     stmt.setFetchSize(maxRecBeforWrite); 
     rs = stmt.executeQuery(); 
     // Write query result to file 
     writeDataToFile(rs, filePath + "/" + fileName, getRecordCount(
       query, conn)); 
    } catch (SQLException sqle) { 
     sqle.printStackTrace(); 
    } finally { 
     try { 
      rs.close(); 
      stmt.close(); 
     } catch (SQLException ex) { 
      throw ex; 
     } 
    } 
} 

private void writeDataToFile(ResultSet rs, String tempFile, String cnt) 
     throws SQLException, Exception { 
    FileOutputStream fileOut = null; 
    int maxLength = 0; 
    try { 
     fileOut = new FileOutputStream(tempFile, true); 
     FileChannel fcOut = fileOut.getChannel(); 

     List<TableMetaData> metaList = getMetaData(rs); 
     maxLength = getMaxRecordLength(metaList); 
     // Write Header 
     writeHeaderRec(fileOut, maxLength); 
     while (rs.next()) { 
      // Now iterate on metaList and fetch all the column values. 
      writeData(rs, metaList, fcOut); 
     } 
     // Write trailer 
     writeTrailerRec(fileOut, cnt, maxLength); 
    } catch (FileNotFoundException fnfe) { 
     fnfe.printStackTrace(); 
    } catch (IOException ioe) { 
     ioe.printStackTrace(); 
    } finally { 
     try { 
      fileOut.close(); 
     } catch (IOException ioe) { 
      fileOut = null; 
      throw new Exception(ioe.getMessage()); 
     } 
    } 
} 

private void writeData(ResultSet rs, List<TableMetaData> metaList, 
     FileChannel fcOut) throws SQLException, IOException { 
    StringBuilder rec = new StringBuilder(); 
    String lf = "\n"; 
    for (TableMetaData tabMeta : metaList) { 
     rec.append(getFormattedString(rs, tabMeta)); 
    } 
    rec.append(lf); 
    ByteBuffer byteBuf = ByteBuffer.wrap(rec.toString() 
      .getBytes("US-ASCII")); 
    fcOut.write(byteBuf); 
} 

private String getFormattedString(ResultSet rs, TableMetaData tabMeta) 
     throws SQLException, IOException { 
    String colValue = null; 
    // check if it is a CLOB column 
    if (tabMeta.isCLOB()) { 
     // Column is a CLOB, so fetch it and retrieve first clobLimit chars. 
     colValue = String.format("%-" + tabMeta.getColumnSize() + "s", 
       getCLOBString(rs, tabMeta)); 
    } else { 
     colValue = String.format("%-" + tabMeta.getColumnSize() + "s", rs 
       .getString(tabMeta.getColumnName())); 
    } 
    return colValue; 

}

+4

Vous n'avez fourni aucun code, ce qui rend difficile de dire exactement ce que vous faites mal ... –

+1

Si vous diffusez les enregistrements dans un fichier vous ne devriez pas avoir de problème, il n'y a aucune raison de conserver une table entière de 2 Go en mémoire avant de l'écrire dans un fichier. –

+0

Vérifiez également comment vous utilisez des chaînes dans votre code. Sans code, il est difficile de suggérer des solutions. –

Répondre

3

C'est probablement dû à la façon dont vous appelez prepareStatement, voir this question pour un problème similaire. Vous n'avez pas besoin et un Scrollability ResultSet sera en lecture seule être par défaut si il suffit d'appeler

stmt = conn.prepareStatement(query); 
+0

Je suis en train de tester ce que vous avez suggéré. Je reviendrai une fois terminé. – Amit

1

Modifier: Carte vos tables de base de données à la classe Usig JPA.
Maintenant, chargez la collection d'objets à partir de la base de données en utilisant Hibernate dans le lot d'une certaine taille tolérable et sérialiser en FICHIER.

+0

Pouvez-vous être un peu plus clair sur ce point? – Amit

0

Votre algorithme est-il similaire à celui-ci? Cela suppose une mise en correspondance directe entre les lignes DB et lignes dans le fichier:

// open file for writing with buffered writer. 
// execute JDBC statement 
// iterate through result set 
    // convert rs to file format 
    // write to file 
// close file 
// close statement/rs/connection etc 

Essayez d'utiliser Spring JDBC modèle pour simplifier la partie JDBC.

+0

Comment Spring JDBC pourrait-il aider à réduire l'utilisation de la mémoire qui est actuellement causée par un énorme processus d'écriture de fichier? – Amit

+1

L'utilisation de 'SimpleJdbcTemplate' résoudrait le problème, car cela vous donnerait un' ResultSet' correctement instancié pour itérer. Mon opinion personnelle est que vous devriez * toujours * utiliser Spring pour aucune autre raison que la hiérarchie 'DataAccessException' –

+0

@Jon Freedman - Je suis d'accord que Spring aide, mais je préférais ne pas l'utiliser ici car il était preety petite fonctionnalité à l'extérieur du réel application. – Amit

0

Je crois que cela doit être possible sur le tas Java 32 Mo par défaut. Il suffit de récupérer chaque ligne, enregistrer les données dans le flux de fichiers, flash et fermer une fois terminé.

+0

Etes-vous sûr que la taille du fichier n'affecterait pas l'utilisation de la taille du tas. Je fais exactement ce que vous dites. J'écris un disque par disque. – Amit

0

Quelle est la valeur que vous utilisez pour maxRecBeforWrite? Peut-être que la requête de la longueur d'enregistrement max vainc votre setFetchSize en forçant JDBC à analyser le résultat entier pour la longueur d'enregistrement? Peut-être que vous pourriez retarder l'écriture de votre en-tête et noter la taille maximale de l'enregistrement à la volée.

+0

maxRecBeforWrite = 100 – Amit