在对来自 DB 的数据进行分页时,您需要知道将有多少页面来呈现页面跳转控件。
目前,我通过运行两次查询来做到这一点,一次包裹在 acount()
中以确定总结果,第二次使用限制来获取当前页面所需的结果。
这似乎效率低下。有没有更好的方法来确定在应用之前会返回多少结果LIMIT
?
我正在使用 PHP 和 Postgres。
在对来自 DB 的数据进行分页时,您需要知道将有多少页面来呈现页面跳转控件。
目前,我通过运行两次查询来做到这一点,一次包裹在 acount()
中以确定总结果,第二次使用限制来获取当前页面所需的结果。
这似乎效率低下。有没有更好的方法来确定在应用之前会返回多少结果LIMIT
?
我正在使用 PHP 和 Postgres。
自 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_count
OFFSET
LIMIT
full_count
极端情况:当OFFSET
至少与基本查询的行数一样多时,不返回任何行。所以你也得到 nofull_count
。可能的替代方案:
SELECT
查询中的事件序列(0. CTE 是单独评估和实现的。在 Postgres 12 或更高版本中,规划器可能会在开始工作之前内联那些类似子查询的内容。)不在这里。
WHERE
子句(和JOIN
条件,尽管在您的示例中没有)从基表中过滤符合条件的行。其余的基于过滤的子集。( 2.GROUP BY
和聚合函数会放在这里。)不在这里。
( 3. 其他SELECT
列表表达式根据分组/聚合列进行评估。)不在这里。
窗口函数的应用取决于OVER
子句和函数的框架规范。简单count(*) OVER()
是基于所有符合条件的行。
ORDER BY
( 6. DISTINCT
or DISTINCT ON
would go here.) 不在这里。
LIMIT
/OFFSET
根据已建立的顺序应用以选择要返回的行。LIMIT
/OFFSET
随着表中行数的增加而变得越来越低效。如果您需要更好的性能,请考虑替代方法:
有完全不同的方法来获取受影响行的计数(不是OFFSET
应用&之前的完整计数LIMIT
)。Postgres 有内部记账多少行受最后一个 SQL 命令影响。一些客户端可以访问该信息或自己计算行数(如 psql)。
例如,您可以在执行 SQL 命令后立即检索plpgsql中受影响的行数:
GET DIAGNOSTICS integer_var = ROW_COUNT;
或者你可以pg_num_rows
在PHP中使用。或其他客户端中的类似功能。
有关的:
正如我在博客中所描述的,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”最有可能被索引(如果没有,你就有问题了)。缺点是如果在页面视图之间添加新元素,这可能会有点不同步(但话又说回来,访问者可能无法观察到它,并且可能会大大提高性能)。
您可以通过不每次都运行 COUNT() 查询来减轻性能损失。在再次运行查询之前缓存页面数,例如 5 分钟。除非您看到大量的 INSERT,否则应该可以正常工作。
由于 Postgres 已经做了一定数量的缓存,这种方法并不像看起来那么低效。绝对不会将执行时间加倍。我们在数据库层中内置了计时器,所以我已经看到了证据。
鉴于您需要了解分页的目的,我建议您运行一次完整的查询,将数据作为服务器端缓存写入磁盘,然后通过您的分页机制提供数据。
如果您运行 COUNT 查询是为了决定是否向用户提供数据(即如果有 > X 条记录,则返回错误),您需要坚持使用 COUNT 方法。