我的问题的简短版本:
如果我在我的客户端代码中持有一个游标引用天文数字巨大的结果集,那么发出“FETCH ALL FROM cursorname”作为我的下一个命令是否荒谬(即完全失去游标点)?或者当我消耗它时,这会慢慢地将数据流回给我(至少在原则上,假设我有一个写得很好的驱动程序坐在我和Postgres之间)?
更多细节
如果我完全理解了事情,那么Postgres游标真的可以处理以下问题[即使它们可以被用(滥用?)用于其他事情,例如从一个函数返回多个不同的结果集]: / p>
注意:RETURN NEXT和RETURN QUERY的当前实现 在从函数返回之前存储整个结果集,如 上面讨论过。这意味着如果PL / pgSQL函数产生一个 非常大的结果集,性能可能很差:数据将被写入 到磁盘以避免内存耗尽,但功能本身不会 返回,直到生成整个结果集。
(参考:https://www.postgresql.org/docs/9.6/static/plpgsql-control-structures.html)
但是(再次,如果我理解正确)当你编写一个返回游标的函数时,整个查询不会被缓冲到内存(和磁盘)中,然后函数的用户才能开始消耗任何东西,而是结果可以被一点一点地消耗掉。 (设置和使用游标的开销更多,但是为了避免对非常大的结果集进行大量缓冲区分配,这是值得的。)
(参考:https://www.postgresql.org/docs/9.6/static/plpgsql-cursors.html#AEN66551)
我想了解这是如何通过线路连接到Postgres服务器上的SELECTS和FETCHES。
在所有情况下,我都在谈论从客户端代码中消耗结果,这些代码在幕后的套接字上与Postgres通信(实际上在我的情况下使用Npgsql库)。
Q1:如果我尝试执行“SELECT * FROM AstronomicallyLargeTable”作为我对Postgres的唯一命令,该怎么办?是否会为整个选择分配所有内存,然后开始向我发送数据?或者它(有效地)生成自己的游标并一次一点地传回数据(在服务器上没有大量额外的缓冲区分配)?
Q2:如果我已经有一个对天文大的结果集的游标引用(比如因为我已经完成了一次往返,并从某个函数中取回了游标引用),然后执行“FETCH ALL FROM” cursorname“通过电线到Postgres?这是愚蠢的,因为它会在发送任何内容之前为Postgres服务器上的所有结果分配所有内存吗?或者“FETCH ALL FROM cursorname”实际上是按照我的意愿工作,在我使用它的时候慢慢地将数据流回来,而Postgres服务器上没有发生任何大量的缓冲区分配?
编辑:进一步澄清
我在问一个案例,我知道我的数据访问层将数据从服务器一次一行地传输到我一行(所以没有那里有大型客户端缓冲区,但是我知道我自己的应用程序一次消耗一行数据然后丢弃它(因此没有客户端缓冲区)。我绝对不想将所有这些行提取到客户端内存中,然后用它们做一些事情。我看到那将是完全愚蠢的!
所以我认为所有问题(对于刚刚描述的用例)都是关于PostgreSQL开始流的时间长度以及它将为FETCH ALL
分配多少内存缓冲区。 IF(并且它是一个很大的'IF'...)PostgreSQL 在开始之前没有分配所有行的巨大缓冲区,如果它一次一行地将行流回Npgsql,快速启动,然后我相信(但请告诉我为什么/如果我错了),FETCH ALL FROM cursorname
还有一个明确的用例!
答案 0 :(得分:5)
经过一些实验后,似乎PostgreSQL的行为如下:
使用SELECT * FROM large
获取多行不会在服务器端创建临时文件,数据会在扫描时进行流式传输。
如果使用返回refcursor
的函数创建服务器端游标并从游标中获取行,则首先在服务器上收集所有返回的行。如果您运行FETCH ALL
。
以下是我使用包含1000000行的表的实验。 work_mem
设置为64kb(最小值)。 log_temp_files
设置为0,以便在服务器日志中报告临时文件。
首次尝试:
SELECT id FROM large;
结果:未创建临时文件。
第二次尝试:
CREATE OR REPLACE FUNCTION lump() RETURNS refcursor
LANGUAGE plpgsql AS
$$DECLARE
c CURSOR FOR SELECT id FROM large;
BEGIN
c := 'c';
OPEN c;
RETURN c;
END;$$;
BEGIN;
SELECT lump();
lump
------
c
(1 row)
FETCH NEXT FROM c;
id
----
1
(1 row)
FETCH NEXT FROM c;
id
----
2
(1 row)
COMMIT;
结果:未创建临时文件。
第三次尝试:
BEGIN;
SELECT lump();
lump
------
c
(1 row)
FETCH all FROM c;
id
---------
1
2
3
...
999999
1000000
(1000000 rows)
COMMIT;
结果:创建了大约140MB的临时文件。
我真的不知道为什么PostgreSQL会这样做。
答案 1 :(得分:1)
你的问题中缺少的一件事是你真的需要一个plpgsql函数而不是内联的sql函数。我只提出它,因为你的描述很简单 - select * from hugetable
。所以我将根据这些信息回答这个问题。
在这种情况下,您的问题不是真正的问题,因为函数调用可以是不可见的。我的观点是,如果您可以将函数编写为内联SQL函数,而您不指明这种或那种方式,则无需担心plpgsql RETURN QUERY
的这一特定限制。
CREATE OR REPLACE FUNCTION foo()
RETURNS TABLE (id INT)
AS
$BODY$
SELECT * FROM bar;
$BODY$
LANGUAGE SQL STABLE;
看看计划:
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM foo() LIMIT 1;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------
Limit (cost=0.00..0.01 rows=1 width=4) (actual time=0.017..0.017 rows=1 loops=1)
Buffers: shared hit=1
-> Seq Scan on bar (cost=0.00..14425.00 rows=1000000 width=4) (actual time=0.014..0.014 rows=1 loops=1)
Buffers: shared hit=1
Planning time: 0.082 ms
Execution time: 0.031 ms
(6 rows)
没有填写完整的结果集然后返回。
https://wiki.postgresql.org/wiki/Inlining_of_SQL_functions
如果你真的需要plpgsql做一些非sql foo,我会在这里推荐其他答案,但这真的需要在这里说。
答案 2 :(得分:-1)
当您需要处理一组天文数据大的数据并使用SELECT * FROM
或RETURN QUERY
时,您不仅需要服务器上的天文大缓冲区,还需要客户端上的缓冲区。然后你需要等待天文数据才能通过网络到达。内部没有使用游标。
当使用CURSOR
时,你可以克服缓冲,但是FETCH ALL
只会是愚蠢的,因为你强迫光标放弃它的设计目的:从数据库零碎地呈现数据。在服务器端,您可以避免缓冲,因为数据是在生成时通过网络发送的,但客户端仍然需要缓冲所有数据。
某些框架(如Hibernate)在后台进行缓冲,但我不知道Npgsql或JDBC驱动程序等低级库中的类似功能。但是这种缓冲也是有代价的,尤其是天文数字大的SELECT * FROM table LIMIT 1000 OFFSET 23950378000
或类似的东西。
在任何一种情况下,如果您确实需要处理如此大量的数据,那么您在处理服务器端方面会更好 ,例如在PL / pgSQL函数中,然后将结果发送到客户端。服务器计算机不仅通常比客户端更强大,还可以避免大部分网络开销。