postgresql - 在 Postgres 中对非常大的结果集正确使用游标

标签 postgresql database-cursor

我的问题的简短版本:

如果我在我的客户端代码中持有一个对天文数字巨大结果集的游标引用,那么发出“FETCH ALL FROM cursorname”作为我的下一个命令是否荒谬(即完全打败游标点)?或者这会在我使用数据时慢慢地将数据流回给我(至少在原则上,假设我有一个写得很好的驱动程序坐在我和 Postgres 之间)?

更多细节

如果我完全理解正确,那么 Postgres 游标真的可以处理以下问题 [即使它们可以用于(滥用?)其他事情,例如从一个函数返回多个不同的结果集]:

Note: The current implementation of RETURN NEXT and RETURN QUERY stores the entire result set before returning from the function, as discussed above. That means that if a PL/pgSQL function produces a very large result set, performance might be poor: data will be written to disk to avoid memory exhaustion, but the function itself will not return until the entire result set has been generated.

(引用: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 库)。

问题 1:如果我尝试执行“SELECT * FROM AstronomicallyLargeTable”作为我通过网络连接到 Postgres 的唯一命令会怎样?这会为整个选择分配所有内存,然后开始将数据发回给我吗?或者它会(有效地)生成自己的游标并一次将数据流回一点(服务器上没有大量额外的缓冲区分配)?

问题 2:如果我已经有了一个对天文数字般大的结果集的游标引用(比如因为我已经完成了一次往返,并从某个函数取回了游标引用),然后我执行“FETCH ALL FROM cursorname"通过电线连接到 Postgres?这很愚蠢吗,因为它会在将任何内容发回给我之前在 Postgres 服务器上 为所有结果分配所有内存?或者“FETCH ALL FROM cursorname”真的会像我希望的那样工作,在我使用数据时缓慢地流回数据,而不会在 Postgres 服务器上发生任何大量缓冲区分配吗?

编辑:进一步说明

我问的是这样一种情况,我知道我的数据访问层一次一行地从服务器向我传输数据(因此没有那里涉及大型客户端缓冲区,但是long 的数据流),我也知道我自己的应用程序一次消耗一行数据,然后丢弃它(因此没有客户端缓冲区)。我绝对不想将所有这些行提取到客户端内存中,然后对它们进行处理。我认为那将是完全愚蠢的!

所以我认为所有的问题(对于刚刚描述的用例)都是关于 PostgreSQL 需要多长时间才能开始流式传输以及它将为 FETCH ALL 分配多少内存缓冲区. IF(这是一个很大的“IF”...)PostgreSQL 不会在开始之前为所有行分配一个巨大的缓冲区,并且如果它一次一个地将行流回 Npgsql,则快速启动,然后我相信(但请告诉我为什么/如果我错了)FETCH ALL FROM cursorname 仍然有一个明确的用例!

最佳答案

经过一些试验,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 会这样。

关于postgresql - 在 Postgres 中对非常大的结果集正确使用游标,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42292341/

相关文章:

postgresql - 如何为nodejs设置 Node 路径(Ubuntu)

postgresql - 在 Postgresql 中使用 varchar 而不是 varchar(n)

ruby - 瘦找不到 pg_ext

php - 将 Mongo 游标正确解析为 PHP

JDBC 连接到非常繁忙的 SQL 2000 : selectMethod=cursor vs selectMethod=direct?

python - 在 Python 中复制光标对象

sql - Postgres 连接所有

postgresql - 如何防止在 PostgreSQL 中仅在一个表上进行预写日志记录?

postgresql - 事务中的游标在go和psql之间的行为不同

mysql - 如何将 SQL Server 游标转换为 MySQL 等效项