PostgreSQL 中的 cursor 和 fetch 是如何工作的

Posted

技术标签:

【中文标题】PostgreSQL 中的 cursor 和 fetch 是如何工作的【英文标题】:How does cursor and fetch work in PostgreSQL 【发布时间】:2019-05-28 00:39:12 【问题描述】:

我想知道CURSORFETCHPostgreSQL 内部是如何工作的。

一开始我以为

    CURSORselect 语句声明时,DB 将执行select 语句,然后将结果存储在DB 内存中。

    当在CURSOR 上调用FETCH 时,DB 只会读取在CURSOR 上移动的结果。

    CURSOR 关闭时,存储的结果将从内存中删除。

如果我的假设是正确的,那么无论select 语句有多复杂,FETCH 的响应时间都应该很短。

但是,当我测试时,FETCH 显示的响应时间比我预期的要差,就像它做了一些我没有预料到的事情。

它们是如何工作的?

--------- 编辑 ---------

以下是我使用实际数据库表进行测试时得到的结果。 (select 语句包含 3 个表的 join 子句,其中一个表有 300 万行)

(   8sec) DECLARE “123" NO SCROLL CURSOR WITH HOLD FOR SELECT .....
(0.04sec) FETCH FORWARD 2 FROM "123";
(   4sec) FETCH FORWARD 10000 FROM "123";

--------- 编辑 ---------

FETCH FORWARD 10000 FROM "123" 的 4 秒响应时间似乎是因为我使用了pgcli(PostgreSQL 客户端工具)。

我不知道为什么,但是在更换客户端工具后,它显然快了 0.04 秒。

【问题讨论】:

SELECT 不会在您声明游标时执行,而是在您第一次使用它时(第一个 FETCH)。另见this answer. @klin 根据我的测试,DECLARE 似乎有所作为。请检查我编辑的问题。 【参考方案1】:

SQL Commands: DECLARE:

在当前实现中,由持有的游标表示的行 被复制到临时文件或内存区域,以便它们保留 可用于后续交易。

这取决于您是使用单个事务中的游标还是使用“WITH HOLD”和多个事务。

如果您使用“WITH HOLD”,那么在调用“DECLARE”的事务的“COMMIT”上,将使用游标中的所有数据创建一个临时表。如果数据量很大,则表会保存到磁盘,因此读取速度可能会稍慢一些。但不会那么慢,因为这应该是对一些合理数量的行进行顺序扫描。

tometzky=> begin;
BEGIN
Time: 0.301 ms
tometzky=> declare c no scroll cursor with hold for select pg_sleep(1) from generate_series(1,6);
DECLARE CURSOR
Time: 1.140 ms
tometzky=> commit;
COMMIT
Time: 6007.180 ms (00:06.007)
tometzky=> fetch forward 3 from c;
 pg_sleep 
----------



(3 rows)

Time: 0.384 ms
tometzky=> fetch forward 3 from c;
 pg_sleep 
----------



(3 rows)

Time: 0.336 ms
tometzky=> fetch forward 3 from c;
 pg_sleep 
----------
(0 rows)

Time: 0.338 ms

当您使用调用 DECLARE 的同一事务中的游标时,只要请求的行数可用,每个 FETCH 就会返回:

tometzky=> begin;
BEGIN
Time: 0.301 ms
tometzky=> declare c no scroll cursor for select pg_sleep(1) from generate_series(1,6);
DECLARE CURSOR
Time: 1.225 ms
tometzky=> fetch forward 3 from c;
 pg_sleep 
----------



(3 rows)

Time: 3004.041 ms (00:03.004)
tometzky=> fetch forward 3 from c;
 pg_sleep 
----------



(3 rows)

Time: 3003.855 ms (00:03.004)
tometzky=> fetch forward 3 from c;
 pg_sleep 
----------
(0 rows)

Time: 0.229 ms
tometzky=> commit;
COMMIT
Time: 0.444 ms

但是,例如,如果您使用的查询需要在最后一步进行排序,则无论如何它都必须先获取所有行才能对它们进行排序。

【讨论】:

感谢您的回答。在这个问题的一个例子中,如果FETCH FORWARD 10000 FROM "123" 需要 4 秒,这仅仅是因为缓慢的获取操作,而不是任何其他操作?如果是这样,4 秒是 10000 行的合理时间吗? 如果你在声明后提交了,那么 fetch 应该总是很快。 我在here 中发布了一个相关问题。我希望你能再次帮助我。

以上是关于PostgreSQL 中的 cursor 和 fetch 是如何工作的的主要内容,如果未能解决你的问题,请参考以下文章

使用 JDBC 时,Oracle 的 REF CURSOR 在 Postgresql 中的等价物是啥?

Python PostgreSQL 语句问题 psycopg2 cursor.execute(Table Union)

如何使用libpqxx中的pqxx :: stateless_cursor类?

PostgreSql jsonb 字段查看

MySQL与python交互

django postgreSQL 连接问题