PostgreSQL 中的 cursor 和 fetch 是如何工作的
Posted
技术标签:
【中文标题】PostgreSQL 中的 cursor 和 fetch 是如何工作的【英文标题】:How does cursor and fetch work in PostgreSQL 【发布时间】:2019-05-28 00:39:12 【问题描述】:我想知道CURSOR
和FETCH
在PostgreSQL
内部是如何工作的。
一开始我以为
当CURSOR
用select
语句声明时,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)