在PostgreSQL中懒惰地消费函数的结果吗?

时间:2019-07-12 21:25:15

标签: sql postgresql plpgsql

我的数据库中有一个函数,该函数返回很多行:

CREATE FUNCTION lots_of_rows(n integer) RETURNS SETOF integer
STABLE LANGUAGE plpgsql AS $$ BEGIN
  FOR i IN 1..10000000 LOOP
    RETURN NEXT i * n;
  END LOOP;
END $$;

毫不奇怪,使用此功能的查询不是很快:

=# EXPLAIN ANALYZE SELECT n FROM lots_of_rows(4) as n;
                                                          QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------
 Function Scan on lots_of_rows n  (cost=0.25..10.25 rows=1000 width=4) (actual time=1867.135..2900.167 rows=10000000 loops=1)
 Planning Time: 0.026 ms
 Execution Time: 3494.365 ms
(3 rows)

这是预料之中的。但是令我沮丧的是,即使我只使用结果行的一小部分,我仍要为此功能的全部费用付费:

=# EXPLAIN ANALYZE SELECT n FROM lots_of_rows(4) as n LIMIT 10;
                                                          QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.25..0.35 rows=10 width=4) (actual time=1863.679..1863.682 rows=10 loops=1)
   ->  Function Scan on lots_of_rows n  (cost=0.25..10.25 rows=1000 width=4) (actual time=1863.675..1863.676 rows=10 loops=1)
 Planning Time: 0.044 ms
 Execution Time: 1872.395 ms
(4 rows)

很显然,这非常浪费。为了进行比较,如果我使用递归视图执行相同的操作,则基本上需要零时间:

CREATE RECURSIVE VIEW lots_of_rows (n) AS
    VALUES (1)
  UNION ALL
    SELECT n+1 FROM lots_of_rows WHERE n < 10000000;
=# EXPLAIN ANALYZE SELECT n * 4 FROM lots_of_rows LIMIT 10;
                                                                     QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=2.95..3.28 rows=10 width=4) (actual time=0.005..0.027 rows=10 loops=1)
   ->  Subquery Scan on lots_of_rows  (cost=2.95..3.96 rows=31 width=4) (actual time=0.005..0.023 rows=10 loops=1)
         ->  CTE Scan on lots_of_rows lots_of_rows_1  (cost=2.95..3.57 rows=31 width=4) (actual time=0.003..0.020 rows=10 loops=1)
               CTE lots_of_rows
                 ->  Recursive Union  (cost=0.00..2.95 rows=31 width=4) (actual time=0.002..0.015 rows=10 loops=1)
                       ->  Result  (cost=0.00..0.01 rows=1 width=4) (actual time=0.001..0.001 rows=1 loops=1)
                       ->  WorkTable Scan on lots_of_rows lots_of_rows_2  (cost=0.00..0.23 rows=3 width=4) (actual time=0.001..0.001 rows=1 loops=9)
                             Filter: (n < 10000000)
 Planning Time: 0.213 ms
 Execution Time: 0.089 ms
(10 rows)

但是,当然,我的函数接受一个参数n,但是视图不能接受参数,因此某些实现细节必须泄漏到我的各个查询中。

当然,这个lots_of_rows函数非常愚蠢,我实际上并没有在任何地方使用它。我的实际函数更加复杂:它接受几个不同的参数,并使用它们构造一个SELECT查询,使用FOR遍历结果,对于某些行,使用RETURN NEXT返回记录。用视图替换特定功能并不是那么简单。

此外,将限制逻辑从我的封闭查询移到函数中并不容易,因为封闭查询有时会在结果中添加各种WHERE条件:

SELECT r.id FROM complicated_function($1, $2, $3, $4) as r
WHERE r.is_public AND r.score > 0 LIMIT 20;

我想我总是可以针对我需要的所有不同条件,向函数中添加大量不同的参数,但是理想情况下,我希望喜欢能够按原样保留我的函数(因为它精确地封装了我想要的抽象),以某种方式将结果按需流式传输给调用者,以使它的行为有点像视图(尽管对查询计划者而言还是不透明的)。这是全部可能吗,还是必须函数的结果在返回之前已在内存中完全实现?

2 个答案:

答案 0 :(得分:0)

Postgres有两种可能的表功能实现:

    基于持久上下文的
  • 行-它仅返回所需的行-CPU开销稍大一点,但是会提前停止-通过此实现,该函数被调用多次,每次仅返回一行。对于此实现,只能使用C语言。

  • 元组存储实现-这就是您的情况-PLpgSQL和非C语言都使用它。调用函数时,将填充特殊结构tuplestore。生成所有行-并返回所有行。 tuplestore的读取器(父节点)可以读取(或不读取)所有行,但是每次都产生所有行时。外部LIMIT子句不会在函数内部下推,因此它对速度没有任何影响。

没有任何其他实现-因此,如果需要极限结果,则必须显式(手动)执行操作(如果要使用更高的编程语言)。

答案 1 :(得分:-1)

我相信您可以通过使函数返回cursor来实现所需的功能。

一个 cursor 应该允许函数调用程序分批而不是一次读取所有行,这样既可以更快地向调用方提供结果,又可以在客户端和服务器上一次减少内存

注意:在维护游标方面,服务器上存在开销。完成后,调用方应显式关闭 cursor (否则它将在事务结束时关闭)。

尤其要查看上面链接中标题为 43.7.3.5的部分。返回游标