当 itersize 小于数据大小且获取数量小于 itersize 时,psycopg2 服务器端游标如何运行?

Man*_*r.M 2 database postgresql psycopg2 server-side python-3.x

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

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.
Run Code Online (Sandbox Code Playgroud)

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

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

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.
Run Code Online (Sandbox Code Playgroud)

然而,在每次迭代中它只打印一条记录。我获得 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.itersize = 10
         db_cursor.execute("SELECT rec_pos FROM schm.test_data;")
         for i in db_cursor:
             print(i)
             print(">>>>>>>>>>>>>>>>>>>")
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

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

Adr*_*ver 5

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

您会发现,itersize仅当您迭代游标时才适用:

for record in cur:
     print record
Run Code Online (Sandbox Code Playgroud)

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

更新。展示如何itersize工作fetchmany()

使用itersizeandfetchmany()与命名游标:

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"

Run Code Online (Sandbox Code Playgroud)

与未命名游标一起使用fetchmany

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

#Log
statement: select * from cell_per
Run Code Online (Sandbox Code Playgroud)

itersize因此,命名游标按迭代或size使用时设置的批次获取行(从服务器)fetchmany(size=n)。而非命名游标将所有行拉入内存,然后根据 中的size设置从那里获取它们fetchmany(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"
Run Code Online (Sandbox Code Playgroud)

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

不迭代指定的游标对象。使用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"
Run Code Online (Sandbox Code Playgroud)

itersize设置,但没有任何效果,因为未迭代指定的游标对象。相反,fetchmany(size=20)服务器端游标在每次调用时都会发送一批 20 条记录。