【问题标题】:When does the PostgreSQL JDBC driver fetch rows after executing a query?执行查询后,PostgreSQL JDBC 驱动程序何时获取行?
【发布时间】:2013-07-04 16:46:26
【问题描述】:

PostgreSQL JDBC driver 版本9.2-1002 在执行查询后何时从服务器获取行?它是在查询执行后立即获取行(在客户端应用程序调用PreparedStatement.executeQuery() 之后)还是在客户端应用程序第一次调用ResultSet.next() 以从结果集中检索一行之后?这是否取决于语句获取大小的值?

【问题讨论】:

  • 你为什么要关心?或者换一种说法:为什么有人会打电话给executeQuery,而在那之后立即打电话给next()
  • 我试图将执行查询所需的时间与获取行和处理结果集所需的时间分开。查询执行时间包括获取行的时间还是结果集处理包括获取时间?
  • 我认为最好的测试方法是编写一个简单的程序,启动一个调试器,添加一些断点并通过wireshark(或类似工具)观察流量。

标签: java postgresql jdbc


【解决方案1】:

如以下程序所示,PreparedStatement.executeQuery() 始终从服务器检索结果集中的行。该程序还演示了语句提取大小如何影响行检索。在语句的默认提取大小为零的情况下,executeQuery() 从服务器检索所有行,ResultSet.next() 从内存而不是服务器检索并返回下一行。 (程序甚至可以在执行查询后关闭连接,next() 仍然可以遍历所有行。)在 fetch size 不为零的情况下,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();
        }
    }
}

【讨论】:

【解决方案2】:

看看这个documentation

默认情况下,驱动程序会一次收集所有查询结果。这对于大型数据集可能不方便,因此 JDBC 驱动程序提供了一种基于数据库游标的 ResultSet 并且仅获取少量的行。

少量行缓存在连接的客户端,当用尽时,通过重新定位游标来检索下一个行块。

进一步read this

通常,libpq 收集 SQL 命令的整个结果并将其作为单个 PGresult 返回给应用程序。对于返回大量行的命令,这可能是不可行的。对于这种情况,应用程序可以在单行模式下使用 PQsendQuery 和 PQgetResult。在这种模式下,每次将结果行返回给应用程序,因为它们是从服务器接收的。

【讨论】:

  • 这并不能准确地告诉我驱动程序何时从服务器获取结果。驱动程序是在客户端应用程序调用 PreparedStatement.executeQuery() 时还是仅在第一次调用 ResultSet.next() 或其他结果集导航方法之一之后检索结果?
  • @DerekMahar 如果您想知道确切的时间,那么您需要查看 PostgreSQL JDBC 驱动程序的源代码。
  • 驱动程序以fetchSize 的数量缓存请求行。每次您请求超出缓存fetchSize 的额外行时,都会请求行,直到结果集用完为止。您可以通过docs.oracle.com/javase/6/docs/api/java/sql/… 修改fetchSize
  • PgJDBC 实际上并不是基于libpq,所以第二个引用并不真正相关。仅供参考。
【解决方案3】:

PG 要求连接为 AutoCommit = false 以获取行作为游标。 因此,如果您使用 Spring jdbc 和 TransactionManagement,您可以使用来自 Transaction 的连接,默认情况下 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);
    }}}}}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多