Как побороть OutOfMemoryError при записи огромного файла - PullRequest
4 голосов
/ 16 сентября 2010

Я пишу полную программу извлечения базы данных в Java. База данных - Oracle, и она огромна. В некоторых таблицах ~ 260 миллионов записей. Программа должна создать один файл для каждой таблицы в определенном формате, поэтому использование Oracle Datapump и т. Д. Не вариант. Кроме того, некоторые политики безопасности компании не позволяют писать процедуру PL / SQL для создания файлов на сервере БД для этого требования. Я должен идти с Java и JDBC.

Проблема, с которой я сталкиваюсь, заключается в том, что, поскольку файлы для некоторых таблиц огромны (~ 30 ГБ), у меня почти не хватает памяти почти каждый раз, даже с 20 ГБ Java Heap. Во время создания файла, когда размер файла превышает размер кучи, даже с одной из самых агрессивных политик GC, процесс, кажется, зависает. Например, если размер файла> 20 ГБ, а размер кучи - 20 ГБ, когда использование кучи достигнет максимального размера кучи, его скорость записи составит 2 МБ в минуту или около того, и на этой скорости потребуется несколько месяцев, чтобы полностью извлечь файл.

Я ищу способ преодолеть эту проблему. Любая помощь будет принята с благодарностью.

Вот некоторые подробности конфигурации системы, которую я имею: Java - JDK1.6.0_14

Конфигурация системы - RH Enterprise Linux (2.6.18), работающая на 4 X Intel Xeon E7450 (6 ядер) @ 2.39GH

RAM - 32 ГБ

База данных Oracle 11g

файл wirting часть кода идет ниже:

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;

}

Ответы [ 5 ]

3 голосов
/ 16 сентября 2010

Вероятно, это связано с тем, как вы звоните prepareStatement, см. в этом вопросе для аналогичной проблемы. Вам не нужна прокрутка, и ResultSet будет по умолчанию только для чтения, поэтому просто наберите

stmt = conn.prepareStatement(query);
1 голос
/ 16 сентября 2010

Редактировать : Сопоставьте таблицы базы данных с классом usig JPA.
Теперь загрузите коллекцию объектов из БД с помощью Hibernate в пакете некоторого допустимого размера и сериализуйте ее в FILE.

0 голосов
/ 16 сентября 2010

Какое значение вы используете для maxRecBeforWrite?

Возможно, запрос максимальной длины записи побеждает ваш setFetchSize, заставляя JDBC сканировать весь результат на предмет длины записи?Возможно, вы могли бы отложить написание вашего заголовка и отметить максимальный размер записи на лету.

0 голосов
/ 16 сентября 2010

Я считаю, что это должно быть возможно по умолчанию 32 МБ кучи Java. Просто извлеките каждую строку, сохраните данные в файл потока, прошейте и закройте, как только это будет сделано.

0 голосов
/ 16 сентября 2010

Ваш алгоритм похож на следующий? Это предполагает прямое сопоставление между строками БД и строками в файле:

// 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

Попробуйте использовать Spring JDBC Template, чтобы упростить часть JDBC.

...