【发布时间】:2015-12-18 21:19:46
【问题描述】:
我想为给定查询选择前 10 条记录。因此,我可以使用以下选项之一:
- 使用 JDBC
Statement.setMaxRows()方法 - 在 SQL 查询中使用 LIMIT 和 OFFSET
这两种方案的优缺点是什么?
【问题讨论】:
标签: java sql postgresql jdbc limit
我想为给定查询选择前 10 条记录。因此,我可以使用以下选项之一:
Statement.setMaxRows() 方法这两种方案的优缺点是什么?
【问题讨论】:
标签: java sql postgresql jdbc limit
要限制 SQL 查询结果集大小,可以使用 SQL:008 语法:
SELECT title
FROM post
ORDER BY created_on DESC
OFFSET 50 ROWS
FETCH NEXT 50 ROWS ONLY
适用于 Oracle 12、SQL Server 2012 或 PostgreSQL 8.4 或更高版本。
对于 MySQL,您可以使用 LIMIT 和 OFFSET 子句:
SELECT title
FROM post
ORDER BY created_on DESC
LIMIT 50
OFFSET 50
使用 SQL 级别的分页的好处是数据库执行计划可以使用这些信息。
所以,如果我们在 created_on 列上有一个索引:
CREATE INDEX idx_post_created_on ON post (created_on DESC)
我们使用LIMIT 子句执行以下查询:
EXPLAIN ANALYZE
SELECT title
FROM post
ORDER BY created_on DESC
LIMIT 50
我们可以看到数据库引擎使用了索引,因为优化器知道只有 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
如果超出限制,多余的行将被静默删除。
这不是很让人放心!
所以,如果我们在 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++;
}
}
我们在 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
因为数据库优化器不知道我们只需要获取 50 条记录,它假定需要扫描所有 5000 行。如果一个查询需要获取大量记录,那么全表扫描的成本实际上比使用索引要低,因此执行计划根本不会使用索引。
我在 Oracle、SQL Server、PostgreSQL 和 MySQL 上运行了这个测试,看起来 Oracle 和 PostgreSQL 优化器在生成执行计划时没有使用
maxRows设置。但是,在 SQL Server 和 MySQL 上,考虑了
maxRowsJDBC 设置,执行计划相当于使用TOP或LIMIT的SQL 查询。您可以自己运行测试,因为它们在我的High-Performance Java Persistence GitHub repository 中可用。
虽然看起来setMaxRows 是一种限制ResultSet 大小的可移植解决方案,但如果数据库服务器优化器不使用JDBC maxRows 属性,则SQL 级别的分页效率要高得多。
【讨论】:
setmaxrows 的优点是可以创建通用语句,在 Postgres、Oracle、Mysql 等中有效 由于 Oracle 使用的是 rownum 语法,postgres - limit,msqsql - top
从速度上看似乎没有区别。
【讨论】:
ResultSet 中的行,而在SQL Server 中,执行SET ROWCOUNT command,并且MySQL 设置sql_select_limit 变量。
在大多数情况下,您希望使用LIMIT 子句,但最终两者都会达到您想要的效果。此答案针对 JDBC 和 PostgreSQL,但适用于使用类似模型的其他语言和数据库。
Statement.setMaxRows 的 JDBC 文档说
如果超出限制,多余的行将被静默删除。
即数据库服务器可能会返回更多行,但客户端会忽略它们。 PostgreSQL JDBC 驱动程序在客户端和服务器端都有限制。对于客户端,请查看maxRows in the AbstractJdbc2ResultSet 的用法。服务端看maxRows in QueryExecutorImpl。
服务器端,PostgreSQL LIMIT documentation 说:
查询优化器在生成查询时会考虑 LIMIT 计划
所以只要查询是合理的,它就会只加载完成查询所需的数据。
【讨论】:
org.postgresql.core.v3.QueryExecutorImpl#sendOneQuery方法。
setFetchSize 提示 JDBC 驱动程序在此语句生成的 ResultSet 对象需要更多行时应从数据库中获取的行数。
setMaxRows 将此 Statement 对象生成的任何 ResultSet 对象可以包含的最大行数限制为给定数。
我猜你可以尝试使用上述 2 个 JDBC API,如果它适用于 100K 记录,你可以尝试使用 setFetchSize。否则,您可以批量获取并形成 ArrayList 并将其返回到您的 Jasper 报告中。
【讨论】:
不确定我是否正确,但我记得过去我参与了一项大型项目,将所有预期返回一行的查询更改为“TOP 1”或 numrows=1。原因是当使用此“提示”时,数据库将停止搜索“下一个可能的匹配项”。在高容量环境中,这确实有所作为。您可以“忽略”客户端或结果集中的多余记录的评论是不够的。您应该尽早避免不必要的读取。但我不知道 JDBC 方法是否将这些 db 特定提示添加到查询 y/n 中。我可能需要测试才能看到和使用它......我不是数据库专家,可以想象我不对,但是“Speedwise 似乎没有区别”可能是一个错误的假设......例如如果您被要求在框中搜索红球并且您只需要一个,那么继续搜索对您来说一个足够的所有地方并没有增加价值......那么指定'TOP 1'很重要......
【讨论】: