JDBC:LIMIT与setmaxrows(Resultset)

Ron*_*yen 5 java postgresql jdbc

我想选择前10条记录,我可以使用以下之一:

  • 在结果集中使用setmaxrows
  • 在sql语句中使用LIMIT和OFFSET

我的问题是:它们之间的优点和缺点是什么?

Vla*_*cea 20

SQL 级别限制

要限制 SQL 查询结果集大小,可以使用 SQL:008 语法:

SELECT title
FROM post
ORDER BY created_on DESC
OFFSET 50 ROWS
FETCH NEXT 50 ROWS ONLY
Run Code Online (Sandbox Code Playgroud)

适用于 Oracle 12、SQL Server 2012 或 PostgreSQL 8.4 或更新版本。

对于 MySQL,您可以使用 LIMIT 和 OFFSET 子句:

SELECT title
FROM post
ORDER BY created_on DESC
LIMIT 50
OFFSET 50
Run Code Online (Sandbox Code Playgroud)

使用 SQL 级分页的好处是数据库执行计划可以使用这些信息。

所以,如果我们在created_on列上有一个索引:

CREATE INDEX idx_post_created_on ON post (created_on DESC)
Run Code Online (Sandbox Code Playgroud)

我们执行以下使用该LIMIT子句的查询:

EXPLAIN ANALYZE
SELECT title
FROM post
ORDER BY created_on DESC
LIMIT 50
Run Code Online (Sandbox Code Playgroud)

我们可以看到数据库引擎使用索引,因为优化器知道只需要提取 50 条记录:

Execution plan:
Limit  (cost=0.28..25.35 rows=50 width=564)
       (actual time=0.038..0.051 rows=50 loops=1)
  ->  Index Scan using idx_post_created_on on post p  
      (cost=0.28..260.04 rows=518 width=564) 
      (actual time=0.037..0.049 rows=50 loops=1)
Planning time: 1.511 ms
Execution time: 0.148 ms
Run Code Online (Sandbox Code Playgroud)

JDBC 语句 maxRows

根据setMaxRowsJavadoc

如果超出限制,多余的行将被静默删除。

这不是很让人放心!

因此,如果我们在 PostgreSQL 上执行以下查询:

try (PreparedStatement statement = connection
    .prepareStatement("""
        SELECT title
        FROM post
        ORDER BY created_on DESC
    """)
) {
    statement.setMaxRows(50);
    ResultSet resultSet = statement.executeQuery();
    int count = 0;
    while (resultSet.next()) {
        String title = resultSet.getString(1);
        count++;
    }
}
Run Code Online (Sandbox Code Playgroud)

我们在 PostgreSQL 日志中得到如下执行计划:

Execution plan:
  Sort  (cost=65.53..66.83 rows=518 width=564) 
        (actual time=4.339..5.473 rows=5000 loops=1)
  Sort Key: created_on DESC
  Sort Method: quicksort  Memory: 896kB
  ->  Seq Scan on post p  (cost=0.00..42.18 rows=518 width=564) 
                          (actual time=0.041..1.833 rows=5000 loops=1)
Planning time: 1.840 ms
Execution time: 6.611 ms 
Run Code Online (Sandbox Code Playgroud)

因为数据库优化器不知道我们只需要获取 50 条记录,所以它假设需要扫描所有 5000 行。如果查询需要获取大量记录,全表扫描的成本实际上比使用索引要低,因此执行计划根本不会使用索引。

我在 Oracle、SQL Server、PostgreSQL 和 MySQL 上运行了这个测试,看起来 Oracle 和 PostgreSQL 优化器maxRows在生成执行计划时不使用该设置。

但是,在 SQL Server 和 MySQL 上,maxRows考虑了JDBC 设置,执行计划相当于使用TOP或的 SQL 查询LIMIT。您可以自己运行测试,因为它们在我的高性能 Java 持久性 GitHub 存储库 中可用

结论

尽管看起来setMaxRows是限制 大小的可移植解决方案ResultSet,但如果数据库服务器优化器不使用 JDBCmaxRows属性,则 SQL 级别的分页效率会更高。


Ole*_*sak 5

setmaxrows的优点是您可以创建在Postgres,Oracle,Mysql等中有效的通用语句。由于Oracle使用rownum语法,因此postgres-limit,msqsql-top

在速度上似乎没有什么区别。

  • ...如果JDBC驱动程序相当智能 (2认同)
  • @CraigRinger:我不相信 PostgreSQL “处理得正确”。从源代码来看,这些信息似乎没有以任何方式发送到服务器。它只是用于停止处理“ResultSet”中的行,而在 SQL Server 中,[“SET ROWCOUNT”命令](https://docs.microsoft.com/en-us/sql/t-sql/statements/执行 set-rowcount-transact-sql?view=sql-server-ver15),MySQL 设置 [`sql_select_limit`](https://dev.mysql.com/doc/refman/8.0/en/server-system -variables.html#sysvar_sql_select_limit) 变量。 (2认同)

Ale*_*lor 4

对于大多数情况,您希望使用该LIMIT子句,但最终两者都会实现您想要的。这个答案针对的是 JDBC 和 PostgreSQL,但也适用于使用类似模型的其他语言和数据库。

JDBC 文档Statement.setMaxRows

如果超出限制,多余的行将被静默删除。

即数据库服务器可能返回更多行,但客户端将忽略它们。PostgreSQL JDBC 驱动程序对客户端和服务器端都有限制。对于客户端,请查看maxRowsAbstractJdbc2ResultSet的用法。对于服务器端,请查看maxRowsinQueryExecutorImpl

服务器端,PostgreSQLLIMIT文档说:

查询优化器在生成查询计划时考虑 LIMIT

因此,只要查询合理,它就会仅加载完成查询所需的数据。

  • 我认为您的说法是错误的:“似乎仅在客户端受到限制”。您可以查看 org.postgresql.core.v3.QueryExecutorImpl#sendOneQuery 方法。 (2认同)