【问题标题】:How does psycopg2 server side cursor operate when itersize is less than data size and fetch number is less than itersize?当 itersize 小于数据大小且 fetch number 小于 itersize 时,psycopg2 服务器端游标如何操作?
【发布时间】:2020-12-16 18:30:21
【问题描述】:

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

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,将它们发送到客户端。我也很想知道 ram 消耗会是什么样子?据我了解,如果执行完整查询占用 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)

基于这两个代码sn-ps,我猜在前面提到的场景中发生的情况是给定下面的代码......

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 将从服务器端的 DB 中获取。但我很困惑,因为这似乎不是文档所暗示的。但是话又说回来......代码似乎暗示了这一点。

【问题讨论】:

    标签: python-3.x database postgresql psycopg2 server-side


    【解决方案1】:

    我会花一些时间在这里Server side cursor

    您会发现itersize 仅在您迭代游标时适用:

    for record in cur:
         print record
    

    由于您使用的是fetchmany(size=100),因此您一次只能处理 100 行。 服务器不会在内存中保存 1000 行。 我错了。游标会将内存中的所有行返回给客户端,如果未使用命名游标,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
    

    因此,命名游标在迭代时以itersize 或在使用fetchmany(size=n) 时由size 设置的批次(从服务器)获取行。而未命名的游标将所有行拉入内存,然后根据在fetchmany(size=n) 中设置的size 从那里获取它们。

    进一步更新

    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"
    

    在上面的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"
    

    itersize 已设置,但它没有作为命名光标对象的效果 没有被迭代。相反,fetchmany(size=20) 让服务器端游标在每次调用时发送一批 20 条记录。

    【讨论】:

    • 查看我的答案的更新。原来我错了。 fetchmany 在命名游标和未命名游标中使用时的操作方式不同。
    • 您必须展示您的代码(提出您的问题,因为这样更容易查看)。迭代仍将一次发生一行。 itersize 所做的是设置它从中提取的一批行的大小。因此,如果itersize 为 10,那么服务器游标将返回 rows
    • 是和不是。 itersize 在迭代命名游标时起作用。 fetchmany() 在您直接从命名游标中获取时有效。
    • 查看我的更多示例。
    • 更正itersizefetchmay 的关系。 itersize 行为几乎就在那里。它将itersize 的批次发送到客户端,然后迭代该批次。因此,批量大小为 100 时,服务器游标将 100 行发送到客户端,然后对其进行迭代。如果服务器游标中有更多行,则发送另一批 100 行,依此类推。目的是一次不发送 1 行,这是非命名游标的默认设置,并且一次不发送整个结果集。您可以调整内存中保存的结果集大小。
    猜你喜欢
    • 2019-10-19
    • 1970-01-01
    • 2016-01-26
    • 1970-01-01
    • 2012-08-31
    • 1970-01-01
    • 2017-05-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多