当 itersize 小于数据大小且 fetch number 小于 itersize 时,psycopg2 服务器端游标如何操作?

How does psycopg2 server side cursor operate when itersize is less than data size and fetch number is less than itersize?

提问人:Mansour.M 提问时间:8/28/2020 最后编辑:Mansour.M 更新时间:8/29/2020 访问量:1912

问:

我已经阅读了文档和几篇文章、帖子和线程,但我不确定我是否清楚地理解了这一点。让我们假设这个场景:

1. I have a server side cursor.
2. I set the itersize to 1000.
3. I execute a SELECT query which would normally return 10000 records.
4. I use fetchmany to fetch 100 records at a time.

我的问题是这是如何在幕后完成的?我的理解是执行查询,但服务器端游标读取了 1000 条记录。光标不会读取下一个 1000,除非它滚动到当前读取的 1000 的最后一条记录。此外,服务器端光标将 1000 保存在服务器的内存中,并一次滚动 100 个,将它们发送到客户端。我也很想知道公羊消耗会是什么样子?根据我的理解,如果执行完整查询占用 10000 kb 的内存,则服务器端游标在服务器上仅消耗 1000 kb,因为它一次仅读取 1000 条记录,而客户端游标将使用 100 kb。我的理解正确吗?

更新根据文档和我们在回复中的讨论,我希望此代码一次打印 10 个项目的列表:

from psycopg2 import connect, extras as psg_extras
    
with connect(host="db_url", port="db_port", dbname="db_name", user="db_user", password="db_password") as db_connection:
     with db_connection.cursor(name="data_operator", 
         cursor_factory=psg_extras.DictCursor) as db_cursor:
         db_cursor.itersize = 10
         db_cursor.execute("SELECT rec_pos FROM schm.test_data;")
         for i in db_cursor:
             print(i)
             print(">>>>>>>>>>>>>>>>>>>")

但是,在每次迭代中,它只打印一条记录。我获得 10 条记录的唯一方法是使用 fetchmany:

from psycopg2 import connect, extras as psg_extras
    
with connect(host="db_url", port="db_port", dbname="db_name", user="db_user", password="db_password") as db_connection:
     with db_connection.cursor(name="data_operator", 
        cursor_factory=psg_extras.DictCursor) as db_cursor:
        db_cursor.execute("SELECT rec_pos FROM schm.test_data;")
         records = db_cursor.fetchmany(10)
         while len(records) > 0:
             print(i)
             print(">>>>>>>>>>>>>>>>>>>")
             records = db_cursor.fetchmany(10)

基于这两个代码片段,我猜在前面提到的场景中发生的事情是,给定下面的代码......

from psycopg2 import connect, extras as psg_extras
    
with connect(host="db_url", port="db_port", dbname="db_name", user="db_user", password="db_password") as db_connection:
    with db_connection.cursor(name="data_operator", 
        cursor_factory=psg_extras.DictCursor) as db_cursor:
        db_cursor.itersize = 1000
        db_cursor.execute("SELECT rec_pos FROM schm.test_data;")
        records = db_cursor.fetchmany(100)
        while len(records) > 0:
            print(i)
            print(">>>>>>>>>>>>>>>>>>>")
            records = db_cursor.fetchmany(100)

...IterSize 是服务器端的东西。它的作用是,当查询运行时,它会设置一个限制,以仅从数据库加载 1000 条记录。但是 fetchmany 是客户端的事情。它从服务器获取 1000 个中的 100 个。每次运行 fetchmany 时,都会从服务器获取下一个 100 个。当服务器端的所有 1000 个都滚动时,接下来的 1000 个将从服务器端的数据库中获取。但我相当困惑,因为这似乎不是文档所暗示的。但话又说回来......代码似乎暗示了这一点。

python-3.x 数据库 postgresql psycopg2 服务器端

评论


答:

4赞 Adrian Klaver 8/28/2020 #1

我会在这里花一些时间 服务器端光标.

您会发现,这仅适用于遍历游标时:itersize

for record in cur:
     print record

由于您正在使用,因此一次只能处理 100 行。服务器不会在内存中保留 1000 行。 我错了。游标会将所有行返回到内存中的客户端,然后在未使用命名游标的情况下,以指定的批大小从那里拉取行。如果使用命名游标,则它将以批处理大小从服务器获取。fetchmany(size=100)fetchmany()

更新。展示工作原理和工作原理。itersizefetchmany()

使用和命名游标:itersizefetchmany()

cur = con.cursor(name='cp')
cur.itersize = 10
cur.execute("select * from cell_per")
for rs in cur:     
   print(rs) 
cur.close()

#Log
statement: DECLARE "cp" CURSOR WITHOUT HOLD FOR select * from cell_per
statement: FETCH FORWARD 10 FROM "cp"
statement: FETCH FORWARD 10 FROM "cp"
statement: FETCH FORWARD 10 FROM "cp"
statement: FETCH FORWARD 10 FROM "cp"
statement: FETCH FORWARD 10 FROM "cp"
statement: FETCH FORWARD 10 FROM "cp"
statement: FETCH FORWARD 10 FROM "cp"
statement: FETCH FORWARD 10 FROM "cp"
statement: CLOSE "cp"

cur = con.cursor(name='cp') 
cur.execute("select * from cell_per")
cur.fetchmany(size=10) 

#Log
statement: DECLARE "cp" CURSOR WITHOUT HOLD FOR select * from cell_per
statement: FETCH FORWARD 10 FROM "cp"

使用未命名的游标:fetchmany

cur = con.cursor()
cur.execute("select * from cell_per")
rs = cur.fetchmany(size=10)
len(rs)                                                                                                                                                                   
10

#Log
statement: select * from cell_per

因此,命名游标在迭代时或使用时按 设置的批处理方式获取行(从服务器)。而非命名游标将所有行拉入内存,然后根据 中的设置从那里获取它们。itersizesizefetchmany(size=n)sizefetchmany(size=n)

进一步更新

itersize仅当循环访问游标对象本身时才适用:

cur = con.cursor(name="cp")
cur.itersize = 10 
cur.execute("select * from cell_per")
for r in cur: 
    print(r) 
cur.close()

#Postgres log:
statement: DECLARE "cp" CURSOR WITHOUT HOLD FOR select * from cell_per
statement: FETCH FORWARD 10 FROM "cp"
statement: FETCH FORWARD 10 FROM "cp"
statement: FETCH FORWARD 10 FROM "cp"
statement: FETCH FORWARD 10 FROM "cp"
statement: FETCH FORWARD 10 FROM "cp"
statement: FETCH FORWARD 10 FROM "cp"
statement: FETCH FORWARD 10 FROM "cp"
statement: FETCH FORWARD 10 FROM "cp"
statement: CLOSE "cp"

在上面,将从服务器端(命名)游标返回的每批 10 行中获取一行。该批大小为 = 。因此,当您循环访问命名游标对象本身时,查询指定的所有行都将在迭代器中返回,只是批量返回 。ritersizeitersize

不循环访问命名游标对象。用:fetchmany(size=n)

cur = con.cursor(name="cp") 
cur.itersize = 10
cur.execute("select * from cell_per") 
cur.fetchmany(size=20)
cur.fetchmany(size=20)
cur.close()

#Postgres log:
statement: DECLARE "cp" CURSOR WITHOUT HOLD FOR select * from cell_per
statement: FETCH FORWARD 20 FROM "cp"
statement: FETCH FORWARD 20 FROM "cp"
CLOSE "cp"

已设置,但它作为命名游标对象不起作用 没有被迭代。取而代之的是,每次调用服务器端游标时都会发送一批 20 条记录。itersizefetchmany(size=20)

评论

1赞 Adrian Klaver 8/28/2020
请参阅更新我的答案。事实证明我错了。fetchmany 在命名游标和未命名游标中使用时操作不同。
1赞 Adrian Klaver 8/29/2020
你必须展示你的代码(把它提出你的问题,因为它会更容易看到)。迭代仍将一次进行一行。所做的是设置它从中拉取的一批行的大小。因此,如果为 10,则服务器游标将返回行 <= 10,然后迭代器将从该批处理中一次拉出一行。如果服务器游标中有超过 10 行,则 psycopg2 游标/迭代器将请求另外 10 行并一次迭代一行。itersizeitersize
1赞 Adrian Klaver 8/29/2020
是的,也不是。 在迭代命名游标时起作用。 当您直接从命名游标中提取时有效。itersizefetchmany()
1赞 Adrian Klaver 8/29/2020
请参阅我的进一步示例。
1赞 Adrian Klaver 9/1/2020
更正了 和 的关系。几乎在行为上。它将批处理发送到客户端,然后客户端循环访问该批处理。因此,当批处理大小为 100 时,服务器游标向客户端发送 100 行,然后客户端循环访问这些行。如果服务器游标中有更多行,则发送另一批 100 行,依此类推。目的是一次不发送 1 行(这是非命名游标的默认设置),并且不会一次发送整个结果集。您可以调整内存中保存的结果集大小。itersizefetchmayitersizeitersize