75

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

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

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

我正在使用 PHP 和 Postgres。

4

5 回答 5

147

纯 SQL

自 2008 年以来情况发生了变化。您可以使用窗口函数在一个查询中获取完整计数有限结果。2009 年与 PostgreSQL 8.4 一起引入。

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

请注意,这可能比没有总计数要昂贵得多。必须计算所有行,并且从匹配索引中仅获取顶行的可能捷径可能不再有用。与小桌子或<= +
无关紧要。事变大了很多。full_countOFFSETLIMITfull_count

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

SELECT查询中的事件序列

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

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

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

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

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

  2. ORDER BY

( 6. DISTINCTor DISTINCT ONwould go here.) 不在这里。

  1. LIMIT/OFFSET根据已建立的顺序应用以选择要返回的行。

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

获得最终计数的替代方案

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

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

GET DIAGNOSTICS integer_var = ROW_COUNT;

手册中的详细信息。

或者你可以pg_num_rowsPHP中使用。或其他客户端中的类似功能。

有关的:

于 2011-11-23T13:12:41.943 回答
6

正如我在博客中所描述的,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”最有可能被索引(如果没有,你就有问题了)。缺点是如果在页面视图之间添加新元素,这可能会有点不同步(但话又说回来,访问者可能无法观察到它,并且可能会大大提高性能)。

于 2008-10-01T04:43:48.727 回答
3

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

于 2008-10-01T05:08:59.883 回答
0

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

于 2009-09-25T20:17:07.840 回答
-1

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

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

于 2008-10-01T04:20:53.743 回答