【问题标题】:SQL LIMIT vs. JDBC Statement setMaxRows. Which one is better?SQL LIMIT 与 JDBC 语句 setMaxRows。哪一个更好?
【发布时间】:2015-12-18 21:19:46
【问题描述】:

我想为给定查询选择前 10 条记录。因此,我可以使用以下选项之一:

这两种方案的优缺点是什么?

【问题讨论】:

    标签: java sql postgresql jdbc limit


    【解决方案1】:

    SQL 级限制

    要限制 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
    

    JDBC 语句 maxRows

    根据setMaxRows Javadoc

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

    这不是很让人放心!

    所以,如果我们在 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 上,考虑了maxRows JDBC 设置,执行计划相当于使用TOPLIMIT 的SQL 查询。您可以自己运行测试,因为它们在我的High-Performance Java Persistence GitHub repository 中可用。

    结论

    虽然看起来setMaxRows 是一种限制ResultSet 大小的可移植解决方案,但如果数据库服务器优化器不使用JDBC maxRows 属性,则SQL 级别的分页效率要高得多。

    【讨论】:

      【解决方案2】:

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

      从速度上看似乎没有区别。

      【讨论】:

      • ...如果 JDBC 驱动程序相当聪明
      • 你掌握2015年一些不聪明的司机信息吗?
      • PostgreSQL 的处理方法是正确的,我相信主要的处理方法是正确的。值得一提的是,它确实需要驱动程序将限制传递给数据库才能合理高效。
      • @CraigRinger:我不相信 PostgreSQL“处理得当”。查看源代码,这些信息似乎并没有以任何方式发送到服务器。它只是用于停止处理ResultSet 中的行,而在SQL Server 中,执行SET ROWCOUNT command,并且MySQL 设置sql_select_limit 变量。
      【解决方案3】:

      在大多数情况下,您希望使用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方法。
      • 我相信你是对的@sibnick。我会更新答案。在不查看服务器源代码的情况下,我猜可能服务器可以忽略这些信息,但这似乎不太可能(而且是一个糟糕的举动)。
      • 在某些情况下,它必须先计算整个结果集,然后才能只返回其中的一部分,但它会尝试选择一个计划来避免这样做。在这些情况下,如果你使用 LIMIT 也是一样的。
      【解决方案4】:

      setFetchSize 提示 JDBC 驱动程序在此语句生成的 ResultSet 对象需要更多行时应从数据库中获取的行数。

      setMaxRows 将此 Statement 对象生成的任何 ResultSet 对象可以包含的最大行数限制为给定数。

      我猜你可以尝试使用上述 2 个 JDBC API,如果它适用于 100K 记录,你可以尝试使用 setFetchSize。否则,您可以批量获取并形成 ArrayList 并将其返回到您的 Jasper 报告中。

      【讨论】:

        【解决方案5】:

        不确定我是否正确,但我记得过去我参与了一项大型项目,将所有预期返回一行的查询更改为“TOP 1”或 numrows=1。原因是当使用此“提示”时,数据库将停止搜索“下一个可能的匹配项”。在高容量环境中,这确实有所作为。您可以“忽略”客户端或结果集中的多余记录的评论是不够的。您应该尽早避免不必要的读取。但我不知道 JDBC 方法是否将这些 db 特定提示添加到查询 y/n 中。我可能需要测试才能看到和使用它......我不是数据库专家,可以想象我不对,但是“Speedwise 似乎没有区别”可能是一个错误的假设......例如如果您被要求在框中搜索红球并且您只需要一个,那么继续搜索对您来说一个足够的所有地方并没有增加价值......那么指定'TOP 1'很重要......

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-09-24
          • 2011-04-04
          • 1970-01-01
          • 2019-08-07
          • 1970-01-01
          • 2017-08-29
          • 2011-01-09
          • 2020-08-22
          相关资源
          最近更新 更多