【问题标题】:How to use server side cursors with psycopg2如何在 psycopg2 中使用服务器端游标
【发布时间】:2017-05-17 15:02:27
【问题描述】:

我有一个有 400 万行的表,我使用 psycopg2 来执行:

   SELECT * FROM ..WHERE query

我以前没有听说过服务器端光标,当您期望有很多结果时,我正在阅读它的一个好习惯。

我发现文档有点有限,我有一些基本问题。

首先我将服务器端游标声明为:

cur = conn.cursor('cursor-name')

然后我将查询执行为:

cur.itersize = 10000
sqlstr = "SELECT clmn1, clmn2 FROM public.table WHERE clmn1 LIKE 'At%'"
cur.execute(sqlstr)

我的问题是:我现在该怎么办?如何获得结果?

我是否遍历行:

row = cur.fetchone()
while row:
   row = cur.fetchone()

或者我使用 fetchmany() 并这样做:

row = cur.fetchmany(10)

但在第二种情况下,我如何“滚动”结果?

还有什么是迭代大小?

【问题讨论】:

    标签: python postgresql psycopg2 database-cursor


    【解决方案1】:

    Psycopg2 有一个很好的界面来处理服务器端的光标。这是一个可以使用的模板:

    with psycopg2.connect(database_connection_string) as conn:
        with conn.cursor(name='name_of_cursor') as cursor:
    
            cursor.itersize = 20000
    
            query = "SELECT * FROM ..."
            cursor.execute(query)
    
            for row in cursor:
                # process row 
    

    上面的代码创建连接并自动将查询结果放入服务器端游标。值itersize 设置客户端一次从服务器端游标下拉的行数。您使用的值应平衡网络调用次数与客户端上的内存使用量。例如,如果您的结果计数为 300 万,则 itersize 值为 2000(默认值)将导致 1500 次网络调用。如果 2000 行消耗的内存很轻,则增加该数字。

    当使用 for row in cursor 时,您当然是一次处理一行,但 Psycopg2 会一次为您预取 itersize 行。

    如果你出于某种原因想使用fetchmany,你可以这样做:

    while True:
        rows = cursor.fetchmany(100)
        if len(rows) > 0:
            for row in rows:
                # process row
        else:
            break
    

    fetchmany 的这种用法不会触发对服务器的网络调用以获取更多行,直到预取的批处理用完为止。 (这是一个令人费解的示例,除了上面的代码之外没有提供任何内容,但演示了如何在需要时使用 fetchmany。)

    【讨论】:

    • 为光标命名至关重要。 Per the docs:“如果指定了名称,则返回的游标将是服务器端游标(也称为命名游标)。否则它将是常规客户端游标”。
    • 您提供的 fetchmany 示例比单独迭代每一行具有优势的一种情况是,如果您还需要查询辅助数据库以比较值并希望减少调用次数。您可以根据 fetchmany 返回的值动态构建对第二个数据库的查询,从而最大限度地减少内存使用和服务器请求。
    • 不管怎样我们在使用服务器端游标时也可以获取列名?
    【解决方案2】:

    除了cur.fetchmany(n),你还可以使用PostgreSQL cursors

    cur.execute("declare foo cursor for select * from generate_series(1,1000000)")
    cur.execute("fetch forward 100 from foo")
    rows = cur.fetchall()
    # ...
    cur.execute("fetch forward 100 from foo")
    rows = cur.fetchall()
    # and so on
    

    【讨论】:

    • @user1919 是的,我看到了。但是服务器端游标在打开时更快。无论如何,您都有选择。尝试两种方式:fetchmany() 在循环中或上面提到的。
    • 嗯。我知道了。但是基于这个答案,我认为我已经使用了服务器端光标:stackoverflow.com/questions/28343240/…
    • 这里他们还描述了制作服务器端光标的相同方法:grokbase.com/t/postgresql/psycopg/11aeyymbc6/…
    • @user1919 看来你是对的。当提供游标名称时,psycopg2 使用相同的方式。好的,如果我的回答没用就提一下 - 我会删除它。
    • @user1919 是的,我上面写的是psycopg2 在内部做的事情,所以你可以使用PostgreSQL doc 来了解发生了什么。祝你好运。
    【解决方案3】:

    当我不想一次加载数百万行时,我倾向于做这样的事情。如果您将数百万行加载到内存中,您可以将程序变成一个非常消耗内存的程序。尤其是当你用这些行或类似的东西制作 python 域对象时。我不确定名称中的 uuid4 是否必要,但我的想法是,如果两个进程进行相同的查询,我希望各个服务器端游标不重叠。

    from uuid import uuid4
    import psycopg2
    
    def fetch_things() -> Iterable[MyDomainObject]:
        with psycopg2.connect(database_connection_string) as conn:
            with conn.cursor(name=f"my_name_{uuid4()}") as cursor:
                cursor.itersize = 500_000
    
                query = "SELECT * FROM ..."
                cursor.execute(query)
    
                for row in cursor:
                    yield MyDomainObject(row)
    

    如果有人知道这是否会在 SQL 服务器上造成存储问题或类似问题,我很感兴趣。

    【讨论】:

      猜你喜欢
      • 2015-08-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-19
      • 1970-01-01
      • 2015-06-20
      • 2019-03-03
      • 1970-01-01
      相关资源
      最近更新 更多