Что на самом деле делает метод Statement.setFetchSize(nSize) в драйвере JDBC для SQL Server?

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

String SQL =  "select col1, col2, coln from mytable where timecol = yesterday";
Statement.executeQuery(SQL);

Проблема в том, что эта программа занимает около 2 ГБ памяти, потому что она принимает все результаты в памяти, а затем обрабатывает ее.

Я пытался установить Statement.setFetchSize(10) но он занимает точно такую ​​же память от ОС, это не имеет никакого значения. Для этого я использую драйвер JDBC Microsoft SQL Server 2005.

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

8 ответов

В JDBC setFetchSize(int) Метод очень важен для производительности и управления памятью в JVM, поскольку он контролирует количество сетевых вызовов из JVM в базу данных и, соответственно, объем оперативной памяти, используемой для обработки ResultSet.

По сути, если setFetchSize (10) вызывается, а драйвер его игнорирует, вероятно, есть только два варианта:

  1. Попробуйте другой драйвер JDBC, который будет учитывать подсказку размера выборки.
  2. Посмотрите на свойства драйвера в соединении (URL и / или карта свойств при создании экземпляра соединения).

RESULT-SET - это количество строк, распределенных в БД в ответ на запрос. ROW-SET - это набор строк, которые выбираются из RESULT-SET за вызов от JVM к БД. Количество этих вызовов и результирующее ОЗУ, необходимое для обработки, зависит от настройки размера выборки.

Поэтому, если RESULT-SET имеет 100 строк и размер выборки равен 10, будет 10 сетевых вызовов для извлечения всех данных, используя примерно 10*{row-content-size} ОЗУ в любой момент времени.

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

Что происходит под ResultSet.next() в том, что он на самом деле не выбирает по одной строке за раз из RESULT-SET. Он извлекает это из (локального) ROW-SET и извлекает следующий ROW-SET (незаметно) с сервера по мере его исчерпания на локальном клиенте.

Все это зависит от драйвера, так как настройка является лишь "подсказкой", но на практике я обнаружил, что именно так он работает для многих драйверов и баз данных (проверено во многих версиях Oracle, DB2 и MySQL).

fetchSize Параметр является подсказкой для драйвера JDBC относительно того, сколько строк можно извлечь за один раз из базы данных. Но водитель может игнорировать это и делать то, что считает нужным. Некоторые драйверы, такие как Oracle, извлекают строки порциями, поэтому вы можете читать очень большие наборы результатов, не занимая много памяти. Другие драйверы просто читают весь набор результатов за один раз, и я предполагаю, что это то, что делает ваш драйвер.

Вы можете попробовать обновить драйвер до версии SQL Server 2008 (что может быть лучше) или драйвера jTDS с открытым исходным кодом.

Вы должны убедиться, что автоматическая фиксация в Соединении отключена, иначе setFetchSize не будет иметь никакого эффекта.

dbConnection.setAutoCommit(false);

Изменить: Вспомнил, что когда я использовал это исправление, оно было специфичным для Postgres, но, надеюсь, он все еще будет работать для SQL Server.

Заявление интерфейса Doc

РЕЗЮМЕ: void setFetchSize(int rows) Дает драйверу JDBC подсказку о количестве строк, которые должны быть извлечены из базы данных, когда требуется больше строк.

Прочитайте эту книгу J2EE и далее Автор Art Taylor

Похоже, что mssql jdbc буферизирует весь набор результатов для вас. Вы можете добавить параметр строки подключения, скажем, selectMode=cursor или responseBuffering=adaptive. Если вы используете версию 2.0+ драйвера mssql jdbc 2005 года, тогда буферизация ответов по умолчанию должна быть адаптивной.

http://msdn.microsoft.com/en-us/library/bb879937.aspx

Мне кажется, что вы действительно хотите ограничить количество строк, возвращаемых в вашем запросе, и просматривать результаты. Если это так, вы можете сделать что-то вроде:

select * from (select rownum myrow, a.* from TEST1 a )
where myrow between 5 and 10 ;

Вам просто нужно определить свои границы.

Попробуй это:

String SQL = "select col1, col2, coln from mytable where timecol = yesterday";

connection.setAutoCommit(false);
PreparedStatement stmt = connection.prepareStatement(SQL, SQLServerResultSet.TYPE_SS_SERVER_CURSOR_FORWARD_ONLY, SQLServerResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(2000);

stmt.set....

stmt.execute();
ResultSet rset = stmt.getResultSet();

while (rset.next()) {
    // ......

У меня была точно такая же проблема в проекте. Проблема в том, что, хотя размер выборки может быть достаточно маленьким, JDBCTemplate считывает весь результат вашего запроса и отображает его в огромном списке, который может разрушить вашу память. Я закончил расширением NamedParameterJdbcTemplate для создания функции, которая возвращает Stream of Object. Этот поток основан на ResultSet, обычно возвращаемом JDBC, но будет извлекать данные из ResultSet только по требованию потока. Это будет работать, если вы не сохраните ссылку на все объекты, которые этот поток плюет. Я действительно вдохновлялся на реализацию org.springframework.jdbc.core.JdbcTemplate#execute(org.springframework.jdbc.core.ConnectionCallback). Единственная реальная разница связана с тем, что делать с ResultSet. В итоге я написал эту функцию, чтобы завершить ResultSet:

private <T> Stream<T> wrapIntoStream(ResultSet rs, RowMapper<T> mapper) {
    CustomSpliterator<T> spliterator = new CustomSpliterator<T>(rs, mapper, Long.MAX_VALUE, NON-NULL | IMMUTABLE | ORDERED);
    Stream<T> stream = StreamSupport.stream(spliterator, false);
    return stream;
}
private static class CustomSpliterator<T> extends Spliterators.AbstractSpliterator<T> {
    // won't put code for constructor or properties here
    // the idea is to pull for the ResultSet and set into the Stream
    @Override
    public boolean tryAdvance(Consumer<? super T> action) {
        try {
            // you can add some logic to close the stream/Resultset automatically
            if(rs.next()) {
                T mapped = mapper.mapRow(rs, rowNumber++);
                action.accept(mapped);
                return true;
            } else {
                return false;
            }
        } catch (SQLException) {
            // do something with this Exception
        }
    }
}

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

Другие вопросы по тегам