Как избежать ошибки OOM (Out of memory) при извлечении всех записей из огромной таблицы? - PullRequest
15 голосов
/ 10 июля 2009

Мне дано задание преобразовать огромную таблицу в пользовательский XML-файл. Я буду использовать Java для этой работы.

Если я просто выдаю «SELECT * FROM customer», он может вернуть огромное количество данных, которые в конечном итоге вызывают OOM. Интересно, есть ли способ, которым я могу обработать запись сразу, как только она станет доступной, и удалить запись из памяти после этого в процессе извлечения sql?

--- отредактировано 13 июля 2009

Позвольте мне уточнить мой вопрос. У меня есть 1 дБ сервер и 1 сервер приложений. Когда я запускаю запрос на выборку в приложении, данные перемещаются с сервера БД на сервер приложений.

Я верю (поправьте меня, если я ошибаюсь) ResultSet нужно будет дождаться получения всех записей в запросе. Даже если мы установим размер выборки равным 4 для таблицы из 1000 записей, у нас все равно останется 1000 записей в динамической памяти сервера приложений, верно? Размер выборки влияет только на число обратных вызовов с / на сервер базы данных.

У меня вопрос: как начать обработку этих 4 (или любого числа) записей сразу после их поступления на сервер приложений и использовать их для освобождения памяти на сервере приложений?

Ответы [ 6 ]

6 голосов
/ 10 июля 2009

Получив немного больше информации, я могу получить более полезный ответ.

Если вы используете MySQL:

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

от http://www.oracle.com/technology/tech/java/sqlj_jdbc/htdocs/jdbc_faq.html:

java.util.Properties info = new java.util.Properties();
info.put ("user", "scott");
info.put ("password","tiger");
info.put ("defaultRowPrefetch","15");
getConnection ("jdbc:oracle:oci:@",info);
4 голосов
/ 10 июля 2009

Одно практическое правило, которое я усвоил из своего опыта, заключается в том, что вы НИКОГДА не переносите ВСЕ данные из базы данных на свой сервер приложений. Одна вещь, которую вы можете сделать, это внедрить процедуру распечатки ваших данных.

Вы можете принести одну страницу данных, содержащую около 1000-5000 записей, обработать их, а затем снова извлечь данные для следующей страницы.

4 голосов
/ 10 июля 2009

Если вы используете JDBC, вы можете использовать ResultSet с курсором, который вы перебираете по одной записи за раз. Вам необходимо убедиться, что вы записываете свой XML в файл по одной записи за раз, а не используете DOM для создания XML.

4 голосов
/ 10 июля 2009

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

1 голос
/ 10 июля 2009

Концепция экспорта всей таблицы. (Примечание для экспертов: я знаю о его недостатках.)

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
public class FullTableExport {
    public static String toXML(String s) {
        if (s != null) {
            StringBuilder b = new StringBuilder(s.length());
            for (int i = 0, count = s.length(); i < count; i++) {
                char c = s.charAt(i);
                switch (c) {
                case '<':
                    b.append("&lt;");
                    break;
                case '>':
                    b.append("&gt;");
                    break;
                case '\'':
                    b.append("&#39;");
                    break;
                case '"':
                    b.append("&quot;");
                    break;
                case '&':
                    b.append("&amp;");
                    break;
                default:
                    b.append(c);
                }
            }
            return b.toString();
        }
        return "";
    }
    public static void main(String[] args) throws Exception {
        String table = "CUSTOMER";
        int batch = 100;

        Class.forName("oracle.jdbc.driver.OracleDriver");
        Connection conn = DriverManager.getConnection(
            "jdbc:oracle:thin:@server:orcl", "user", "pass");
        PreparedStatement pstmt = conn.prepareStatement(
            "SELECT /*+FIRST_ROWS(" + batch + ") */ * FROM " + table);
        ResultSet rs = pstmt.executeQuery();
        rs.setFetchSize(batch);
        ResultSetMetaData rsm = rs.getMetaData();
        File output = new File("result.xml");
        PrintWriter out = new PrintWriter(new BufferedWriter(
            new OutputStreamWriter(
            new FileOutputStream(output), "UTF-8")), false);
        out.printf("<?xml version='1.0' encoding='UTF-8'?>%n");
        out.printf("<table name='%s'>%n", toXML(table));
        int j = 1;
        while (rs.next()) {
            out.printf("\t<row id='%d'>%n", j++);
            for (int i = 1; i <= rsm.getColumnCount(); i++) {
                out.printf("\t\t<col name='%s'>%s</col>%n", 
                    toXML(rsm.getColumnName(i)), 
                    toXML(rs.getString(i)));
            }
            out.printf("\t</row>%n");
        }
        out.printf("</table>%n", table);
        out.flush();
    }
}

Редактировать Недостатки (спасибо @ J.S.):

  • Внешние библиотеки не используются, кроме ojdbc
  • Ничего не закрыто
  • генерируется общее исключение
  • Это основной метод
  • Использование печати для генерации XML
  • Oracle специфический SQL
  • Простой текстовый пароль
  • Некоторые столбцы выглядят неловко в строковом представлении
  • UTF-8 слишком международный
  • Большой объем структуры XML
0 голосов
/ 10 июля 2009

На каком этапе происходит ошибка OOM, происходит ли это при извлечении данных или обработке данных в файл XML?

Если его поиск данных, получить данные в пакетном режиме. Сначала получите общее количество строк, упорядочите выборки по первичному ключу и ограничьте выбранные строки размерами для жевания.

Если при создании файла XML отправьте узел XML каждого клиента в System.out.println, не храните его в памяти. Запустите программу через запятую и перенаправьте весь вывод в файл;

java MyConverter > results.txt

Когда вы перебираете запись, все сохраняется в файле.

...