Что действительно делает метод Statement.setFetchSize (nSize) в драйвере JDBC для SQL Server? - PullRequest
48 голосов
/ 23 августа 2009

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

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 ]

61 голосов
/ 22 марта 2013

В 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} RAM в любой момент времени.

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

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

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

25 голосов
/ 30 декабря 2009

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

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

14 голосов
/ 26 августа 2009

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

dbConnection.setAutoCommit(false);

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

4 голосов
/ 23 августа 2009

Интерфейс выписки Документ

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

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

3 голосов
/ 30 декабря 2009

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

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

1 голос
/ 14 февраля 2018

У меня была точно такая же проблема в проекте. Проблема в том, что, хотя размер выборки может быть достаточно мал, 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
        }
    }
}

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

1 голос
/ 04 июля 2013

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

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()) {
    // ......
1 голос
/ 23 августа 2009

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...