我的数据库中有一个函数,该函数返回很多行:
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;
我想我总是可以针对我需要的所有不同条件,向函数中添加大量不同的参数,但是理想情况下,我希望喜欢能够按原样保留我的函数(因为它精确地封装了我想要的抽象),以某种方式将结果按需流式传输给调用者,以使它的行为有点像视图(尽管对查询计划者而言还是不透明的)。这是全部可能吗,还是必须函数的结果在返回之前已在内存中完全实现?
答案 0 :(得分:0)
Postgres有两种可能的表功能实现:
行-它仅返回所需的行-CPU开销稍大一点,但是会提前停止-通过此实现,该函数被调用多次,每次仅返回一行。对于此实现,只能使用C语言。
元组存储实现-这就是您的情况-PLpgSQL和非C语言都使用它。调用函数时,将填充特殊结构tuplestore
。生成所有行-并返回所有行。 tuplestore的读取器(父节点)可以读取(或不读取)所有行,但是每次都产生所有行时。外部LIMIT
子句不会在函数内部下推,因此它对速度没有任何影响。
没有任何其他实现-因此,如果需要极限结果,则必须显式(手动)执行操作(如果要使用更高的编程语言)。
答案 1 :(得分:-1)
我相信您可以通过使函数返回cursor来实现所需的功能。
一个 cursor 应该允许函数调用程序分批而不是一次读取所有行,这样既可以更快地向调用方提供结果,又可以在客户端和服务器上一次减少内存
注意:在维护游标方面,服务器上存在开销。完成后,调用方应显式关闭 cursor (否则它将在事务结束时关闭)。
尤其要查看上面链接中标题为 43.7.3.5的部分。返回游标。