如何通过 JDBC 告诉 PostgreSQL 我不会获取查询结果的每一行(即如何有效地流式传输结果集的头部)?

dan*_*uss 5 postgresql jdbc jooq

很多时候,我只想N从查询中检索第一行,但我事先不知道N会是什么。例如:

try(var stream = sql.selectFrom(JOB)
                    .where(JOB.EXECUTE_AFTER.le(clock.instant()))
                    .orderBy(JOB.PRIORITY.asc())
                    .forUpdate()
                    .skipLocked()
                    .fetchSize(100)
                    .fetchLazy()) {
    // Now run as many jobs as we can in 10s
    ...
}
Run Code Online (Sandbox Code Playgroud)

现在,在不添加任意 LIMIT 子句的情况下,PG 查询规划器有时会决定对此类查询进行极其缓慢的顺序表扫描,据我所知,因为它认为我将获取结果集中的每一行。任意 LIMIT 类型适用于像这样的简单情况,但我根本不喜欢它,因为:

  1. 限制只是为了“欺骗”查询规划器做正确的事情,它不存在,因为我的目的是获取最多N行。
  2. 当它变得更加复杂并且您有多个这样的查询并且在某种程度上相互依赖时,选择一个N足够大的查询而不破坏您的代码可能会很困难。您不想成为下一个必须理解该代码的人。
  3. 发现查询意外地慢通常发生在生产环境中,其中表包含几百万/十亿行。如果数据库不坚持比开发人员更聪明,那么这是完全可以避免的。
  4. 我厌倦了编写详细的注释来解释为什么查询必须看起来像这样(即解释为什么如果我不添加这个任意限制查询规划器就会搞砸)

那么,我如何告诉查询规划器我只会获取几行,并且快速获取第一行是这里的优先事项?这可以使用 JDBC API/驱动程序来实现吗?

(注意:我不是在寻找间接影响查询规划器的服务器配置调整,例如修改随机页面成本,也不能接受类似的解决方法set seq_scan=off

(注2:为了清楚起见,我在示例代码中使用了 jOOQ,但在幕后这只是另一个PreparedStatement使用ResultSet.TYPE_FORWARD_ONLYand ResultSet.CONCUR_READ_ONLY,所以据我所知,我们可以排除错误的语句模式)

(注3:我没有处于自动提交模式)

Lau*_*lbe 5

PostgreSQL 比你想象的更聪明。您需要做的就是使用该方法将 的获取大小设置java.sql.Statement为不同于 0 的值setFetchSize。然后 JDBC 驱动程序将创建一个游标并以块的形式获取结果集。以这种方式计划的任何查询都将针对快速获取前 10% 的数据进行优化(这由 PostgreSQL 参数控制)cursor_tuple_fraction)。即使查询对表执行顺序扫描,也不必读取所有行:一旦不再获取结果行,读取就会停止。

我不知道如何将 JDBC 方法与 ORM 结合使用,但应该有办法。