Потоковые большие наборы результатов с MySQL - PullRequest
43 голосов
/ 15 марта 2010

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

Я пытался использовать

statement.setFetchSize(Integer.MIN_VALUE);

но каждый открываемый мной ResultSet зависает на close(); Посмотрев в Интернете, я обнаружил, что это происходит потому, что он пытается загрузить непрочитанные строки перед закрытием ResultSet, но это не так, поскольку я делаю это:

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

Зависания случаются и для небольших таблиц (3 строки), и если я не закрою RecordSet (что произошло одним методом), то connection.close() зависает.


Стеки следов зависания:

SocketInputStream.socketRead0 (FileDescriptor, byte [], int, int, int) строка: недоступно [собственный метод]
Строка SocketInputStream.read (byte [], int, int): 129
Строка ReadAheadInputStream.fill (int): 113
ReadAheadInputStream.readFromUnderlyingStreamIfNe Необходимая (byte [], int, int) строка: 160
Строка ReadAheadInputStream.read (byte [], int, int): 188
MysqlIO.readFully (InputStream, byte [], int, int) строка: 2428 Строка MysqlIO.reuseAndReadPacket (Buffer, int): 2882
Строка MysqlIO.reuseAndReadPacket (Buffer): 2871
Строка MysqlIO.checkErrorPacket (int): 3414
Строка MysqlIO.checkErrorPacket (): 910
Строка MysqlIO.nextRow (Field [], int, boolean, int, boolean, boolean, boolean, Buffer): 1405
RowDataDynamic.nextRecord () строка: 413
RowDataDynamic.next () строка: 392 RowDataDynamic.close () строка: 170
JDBC4ResultSet (ResultSetImpl) .realClose (логическое) строка: 7473 JDBC4ResultSet (ResultSetImpl) .close () строка: 881 DelegatingResultSet.close () строка: 152
DelegatingResultSet.close () строка: 152
DelegatingPreparedStatement (DelegatingStatement) .close () строка: 163
(Это мой класс). Database.close () строка: 84

Ответы [ 6 ]

56 голосов
/ 15 марта 2010

Только установка размера выборки не является правильным подходом. Javadoc Statement#setFetchSize() уже заявляет следующее:

Предоставляет драйверу JDBC подсказку относительно количества строк, которые следует извлечь из базы данных

Водитель может свободно применять или игнорировать подсказку. Некоторые драйверы игнорируют его, некоторые драйверы применяют его напрямую, некоторым драйверам нужно больше параметров. Драйвер MySQL JDBC относится к последней категории. Если вы посмотрите документацию по драйверу MySQL JDBC , вы увидите следующую информацию (прокрутите примерно на 2/3 до заголовка ResultSet ):

Чтобы включить эту функцию, вам нужно создать экземпляр Statement следующим образом:

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

Пожалуйста, прочитайте весь раздел документа, он также описывает предостережения этого подхода. Вот соответствующий цитата:

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

(...)

Если оператор находится в рамках транзакции, блокировки снимаются по завершении транзакции (что подразумевает, что сначала необходимо выполнить инструкцию). Как и в большинстве других баз данных, операторы не завершены до тех пор, пока не будут прочитаны все результаты, ожидающие этого оператора, или пока не будет закрыт активный набор результатов для оператора.

Если это не исправляет OutOfMemoryError (не Exception), то, скорее всего, проблема в том, что вы сохраняете все данные в памяти Java, а не обрабатываете их немедленно , как только данные поступают. Для этого потребуется больше изменений в вашем коде, возможно, полное переписывание. Я отвечал на аналогичный вопрос раньше здесь .

12 голосов
/ 15 марта 2010

Не закрывайте ResultSet с дважды.

Очевидно, что при закрытии Statement он пытается закрыть соответствующий ResultSet, как вы можете видеть в этих двух строках из трассировки стека:

DelegatingResultSet.close () строка: 152
DelegatingPreparedStatement (DelegatingStatement) .close () строка: 163

Я думал, что зависание было в ResultSet.close(), но на самом деле это было в Statement.close(), что вызывает ResultSet.close(). Поскольку ResultSet уже был закрыт, он просто завис.

Мы заменили все ResultSet.close() на results.getStatement().close() и удалили все Statement.close() s, и проблема теперь решена.

4 голосов
/ 19 декабря 2012

Если у кого-то возникла такая же проблема, я решил ее, используя в своем запросе предложение LIMIT.

Эта проблема была сообщена MySql как ошибка (найдите ее здесь http://bugs.mysql.com/bug.php?id=42929), которая теперь имеет статус «не ошибка». Самая важная часть:

В настоящее время нет способа закрыть набор результатов "midstream"

Поскольку вам нужно прочитать ВСЕ строки, вам придется ограничить результаты запроса с помощью предложения типа WHERE или LIMIT. Или попробуйте следующее:

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

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

rs.close();

Возможно, это не идеал, но, по крайней мере, это поможет вам преодолеть зависание.

1 голос
/ 07 февраля 2012

Если вы используете spring jdbc, вам нужно использовать создатель подготовленного состояния вместе с SimpleJdbcTemplate, чтобы установить fetchSize как Integer.MIN_VALUE. Это описано здесь http://neopatel.blogspot.com/2012/02/mysql-jdbc-driver-and-streaming-large.html

0 голосов
/ 27 апреля 2018

Scrollable Resultset игнорирует fetchSize и извлекает все строки сразу, вызывая ошибку из-за ошибки памяти.

Для меня это работало правильно при установке useCursors = true, в противном случае Scrollable Resultset игнорирует все реализации размера выборки, в моем случае это было 5000, но Scrollable Resultset извлекал миллионы записей одновременно, вызывая чрезмерное использование памяти. лежащая в основе БД - MSSQLServer.

JDBC: jtds: SQLServer: // локальный: 1433 / ACS; TDS = 8,0; useCursors = истина

0 голосов
/ 09 августа 2013

Зависает, потому что даже если вы перестанете слушать, запрос все равно будет продолжаться.Чтобы закрыть ResultSet и Statement в правильном порядке, попробуйте сначала вызвать Statement.cancel ():

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;
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...