【问题标题】:Best way to get result count before LIMIT was applied在应用 LIMIT 之前获取结果计数的最佳方法
【发布时间】:2010-09-14 10:36:29
【问题描述】:

在对来自 DB 的数据进行分页时,您需要知道将有多少页面来呈现页面跳转控件。

目前,我通过运行两次查询来做到这一点,一次包含在 count() 中以确定总结果,第二次应用限制以获取当前页面所需的结果。

这似乎效率低下。有没有更好的方法来确定在应用LIMIT 之前会返回多少个结果?

我正在使用 PHP 和 Postgres。

【问题讨论】:

    标签: php sql postgresql window-functions sql-limit


    【解决方案1】:

    纯 SQL

    自 2008 年以来情况发生了变化。您可以使用 window function 在一个查询中获取完整计数有限结果。由PostgreSQL 8.4 in 2009 介绍。

    SELECT foo
         , count(*) OVER() AS full_count
    FROM   bar
    WHERE  <some condition>
    ORDER  BY <some col>
    LIMIT  <pagesize>
    OFFSET <offset>;

    请注意,这可能比没有总数的情况要昂贵得多。必须对所有行进行计数,并且从匹配索引中仅获取顶行的可能快捷方式可能不再有用。
    与小桌子或full_count OFFSET + LIMIT 无关紧要。对于更大的full_count 来说很重要。

    极端情况:当OFFSET 至少与基本查询的行数一样多时,无行 返回。所以你也没有得到full_count。可能的替代方案:

    SELECT 查询中的事件序列

    (0。CTE 是单独评估和实现的。在 Postgres 12 或更高版本中,规划器可能会在开始工作之前内联那些类似子查询的内容。)不在这里。

    1. WHERE 子句(和 JOIN 条件,尽管在您的示例中没有)从基表中过滤符合条件的行。 其余部分基于过滤后的子集。

    (2.GROUP BY 和聚合函数会放在这里。)不在这里。

    (3. 其他SELECT 列表表达式根据分组/聚合列进行评估。)不在这里。

    1. 窗口函数的应用取决于OVER 子句和函数的框架规范。简单的count(*) OVER() 是基于所有符合条件的行。

    2. ORDER BY

    (6. DISTINCTDISTINCT ON 会在这里。)不在这里。

    1. LIMIT / OFFSET 将根据既定顺序应用以选择要返回的行。

    LIMIT / OFFSET 随着表中行数的增加变得越来越低效。如果您需要更好的性能,请考虑替代方法:

    获得最终计数的替代方案

    获取受影响行数的方法完全不同(不是在应用OFFSETLIMIT 之前的完整计数)。 Postgres 有内部记账多少行受最后一个 SQL 命令影响。一些客户端可以访问该信息或自己计算行数(如 psql)。

    例如,您可以在执行 SQL 命令后立即检索 plpgsql 中受影响的行数:

    GET DIAGNOSTICS integer_var = ROW_COUNT;
    

    Details in the manual.

    或者您可以使用pg_num_rows in PHP。或其他客户端中的类似功能。

    相关:

    【讨论】:

    • 在旧版本的 postgres 中,您可以对游标执行类似的操作。例如BEGIN; DECLARE c CURSOR FOR SELECT * FROM table; MOVE FORWARD 100 IN c; FETCH 10 FROM c; MOVE FORWARD ALL IN c; COMMIT;。您可以从 FETCH 取回数据,并且可以根据需要从 pg_affected_rows 调用中计算行数。
    • 这会为总数增加一列full_count,对有限结果中的每一行重复相同的总数。这对我来说似乎有点多余和低效。我不确定 PostgreSQL 是否更慢,但我认为这将导致数据库服务器必须向应用程序服务器发送更多数据,对吧?使用WITH 查询获取一行中的总数,然后使用UNION 得到有限结果会更好(更快、更高效)吗?
    • @mattdipasquale:CTE 通常要慢得多(开销更大)。试试吧。您可以使用带有GET DIAGNOSTICS的plpgsql函数获取行数
    • 但是pg_num_rows不应该返回限制后的结果数?
    • @denoise:你是对的,这不是很清楚。我澄清以避免误解。
    【解决方案2】:

    由于 Postgres 已经做了一定数量的缓存,这种方法并不像看起来那么低效。绝对不会将执行时间加倍。我们在 DB 层中内置了计时器,所以我已经看到了证据。

    【讨论】:

      【解决方案3】:

      您可以通过不每次都运行 COUNT() 查询来减轻性能损失。在再次运行查询之前缓存页面数,例如 5 分钟。除非您看到大量 INSERT,否则应该可以正常工作。

      【讨论】:

        【解决方案4】:

        正如我描述的on my blog,MySQL 有一个称为SQL_CALC_FOUND_ROWS 的特性。这消除了执行两次查询的需要,但它仍然需要执行整个查询,即使限制子句允许它提前停止。

        据我所知,PostgreSQL 没有类似的功能。进行分页时要注意的一件事(恕我直言,最常见的是使用 LIMIT):执行“OFFSET 1000 LIMIT 10”意味着数据库必须获取 至少 1010 行,即使如果它只给你 10。一个更高效的方法是记住你为前一行排序的行的值(在这种情况下是第 1000 行)并像这样重写查询:“... WHERE order_row > value_of_1000_th LIMIT 10"。优点是“order_row”最有可能被索引(如果没有,你就有问题了)。缺点是如果在页面视图之间添加新元素,这可能会有点不同步(但话又说回来,访问者可能无法观察到它,并且可能会大大提高性能)。

        【讨论】:

          【解决方案5】:

          鉴于您需要了解分页的目的,我建议您运行一次完整的查询,将数据作为服务器端缓存写入磁盘,然后通过您的分页机制提供数据。

          如果您运行 COUNT 查询是为了决定是否向用户提供数据(即如果有 > X 条记录,则返回错误),您需要坚持使用 COUNT 方法。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2019-01-13
            • 2014-07-21
            • 1970-01-01
            • 2010-09-13
            • 2010-09-06
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多