Когда драйвер PostgreSQL JDBC извлекает строки после выполнения запроса?

#java #postgresql #prepared-statement

#java #postgresql #jdbc

Вопрос:

Когда драйвер PostgreSQL JDBC версии 9.2-1002 извлекает строки с сервера после выполнения запроса? Извлекает ли он строки сразу после выполнения запроса (после вызова клиентского приложения PreparedStatement.executeQuery() ) или после первого вызова клиентского приложения ResultSet.next() для извлечения строки из результирующего набора? Зависит ли это от значения размера выборки оператора?

Комментарии:

1. Почему вас это волнует? Или иначе: почему кто-то должен звонить executeQuery , а не звонить next() сразу после этого?

2. Я пытаюсь отделить время, необходимое для выполнения запроса, от времени, необходимого для извлечения строк и обработки результирующего набора. Включает ли время выполнения запроса время выборки строк или обработка результирующего набора включает время выборки?

3. Я думаю, что лучший способ проверить это — написать простую программу, запустить отладчик, добавить несколько точек останова и наблюдать за трафиком через wireshark (или аналогичный инструмент).

Ответ №1:

Как демонстрирует следующая программа, PreparedStatement.executeQuery() всегда извлекает строки из результирующего набора с сервера. Программа также демонстрирует, как размер выборки операторов влияет на извлечение строк. В случае, когда оператор имеет размер выборки по умолчанию, равный нулю, executeQuery() извлекает все строки с сервера и ResultSet.next() извлекает и возвращает следующую строку из памяти, а не с сервера. (Программа может даже закрыть соединение после выполнения запроса и next() все еще может выполнять итерации по всем строкам.) В случае, когда размер выборки отличен от нуля, executeQuery() извлекает первую партию строк, количество которых равно размеру выборки, и ResultSet.next() снова возвращает следующую строку из памяти до тех пор, пока не будут использованы все строки в текущем пакете, после чего он извлекает следующую партию строк с сервера. Этот шаблон повторяется до ResultSet.next() тех пор, пока с сервера не будет получен пустой пакет (содержащий нулевые строки).

SQL

 -- Create table "test" and insert 2,000,000 integers from 1 up to 2,000,000.
WITH RECURSIVE t(n) AS
(
  VALUES (1)
  UNION ALL
  SELECT n 1
  FROM t
  WHERE n < 2000000
)
SELECT n as value
INTO test
FROM t;
  

Java

 import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;
import java.util.Properties;

public class Start
{
    public static void main( String[] args ) throws InterruptedException, SQLException
    {
        try
        {
            Class.forName( "org.postgresql.Driver" );
        }
        catch ( ClassNotFoundException e )
        {
            System.out.println( "Where is your JDBC Driver?" );
            e.printStackTrace();
            return;
        }

        System.out.println( "Registered JDBC driver" );
        Connection connection = null;

        try
        {
            final String databaseUrl = "jdbc:postgresql://localhost:5483/postgres";
            final Properties properties = new Properties();
            connection = DriverManager.getConnection( databaseUrl, properties );
            connection.setAutoCommit(false);
            Statement statement = connection.createStatement();

            // Default fetch size of 0 does not create a cursor.
            // Method executeQuery will retrieve all rows in result set.
            statement.setFetchSize( 0 );

            // Fetch size of 1 creates a cursor with batch size of 1.
            // Method executeQuery will retrieve only 1 row in the result set.
            //statement.setFetchSize( 1 );

            System.out.println( new Date()   ": Before execute query" );
            ResultSet result =
                statement.executeQuery( "select * from test" );
            System.out.println( new Date()   ": After execute query" );
            System.out.println( new Date()   ": Sleep for 5 s" );
            Thread.sleep( 5000 );
            System.out.println( new Date()   ": Before process result set" );
            while ( result.next() );
            System.out.println( new Date()   ": After process result set" );
            result.close();
            statement.close();
        }
        catch ( SQLException e )
        {
            System.out.println( "Connection failed!" );
            e.printStackTrace();
            return;
        }
        finally
        {
            if ( connection != null )
                connection.close();
        }
    }
}
  

Комментарии:

1. Вы можете установить размер выборки в jdbc

Ответ №2:

Посмотрите на эту документацию

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

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

Далее прочитайте это

Обычно libpq собирает весь результат SQL-команды и возвращает его приложению в виде одного PGresult. Это может быть неработоспособным для команд, которые возвращают большое количество строк. Для таких случаев приложения могут использовать PQsendQuery и PQgetResult в однорядном режиме. В этом режиме результирующая строка (строки) возвращается в приложение по одному, по мере их получения с сервера.

Комментарии:

1. Это не говорит мне точно, когда драйвер извлекает результаты с сервера. Драйвер извлекает результаты при вызове клиентского приложения PreparedStatement.executeQuery() или только после первого вызова ResultSet.next() или одного из других методов навигации по набору результатов?

2. @DerekMahar Если вы хотите точно знать, когда, то вам нужно посмотреть на источник драйвера PostgreSQL JDBC.

3. Драйвер кэширует строки запросов в fetchSize количествах. Каждый раз, когда вы запрашиваете дополнительную строку сверх того, что находится в кэше fetchSize , строки являются запросами до тех пор, пока не будет исчерпан результирующий набор. Вы можете изменить fetchSize с помощью docs.oracle.com/javase/6/docs/api/java/sql /…

4. PgJDBC на самом деле не основан libpq , поэтому вторая цитата на самом деле не актуальна. Просто К вашему сведению.

Ответ №3:

PG требует, чтобы соединение было AutoCommit = false для получения строк в качестве курсора. Итак, если вы используете Spring jdbc и TransactionManagement, вы можете использовать соединение из транзакции, которое по умолчанию имеет значение AutoCommit = false .

 @Repository()
public class SampleRepoImpl implements SampleRepo {

    private static final String SQL = "select s.id from generate_series(1,100000) as s(id)";

    @Autowired
    private DataSource dataSource;

    @Override
    @Transactional(readOnly = true)
    public void findAsCursor(RecordProcessor recordProcessor) throws SQLException {

        // It shouldn't close by hands, it will when the transaction finished.
        Connection connection = DataSourceUtils.getConnection(dataSource);

        try (PreparedStatement ps = connection.prepareStatement(SQL, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
            ps.setFetchSize(100);
            try (ResultSet rs = ps.executeQuery();) {
                while (rs.next()) {
                    long id = rs.getLong("id");
                    System.out.println("id = "   id);
    }}}}}