如何在psycopg2中使用服务器端游标

时间:2017-01-03 13:43:16

标签: python postgresql cursor psycopg2

我有一张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)

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

还有什么意义呢?

3 个答案:

答案 0 :(得分:22)

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。)

答案 1 :(得分: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

答案 2 :(得分:1)

当我不想一次加载数百万行时,我倾向于这样做。如果将数百万行加载到内存中,则可以将程序变成大量的内存。特别是如果您要从这些行或类似的内容中制作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 Server上或类似的东西上造成存储问题。