web-dev-qa-db-ja.com

psycopg2でサーバー側カーソルを使用する方法

400万行のテーブルがあり、psycopg2を使用してaを実行します。

   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)

しかし、2番目のケースでは、結果を「スクロール」する方法を教えてください。

Itersizeのポイントは何ですか?

15
user1919

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
Abelisto

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を使用する場合、もちろん一度に1つの行を処理しますが、Psycopg2は一度にitersize行をプリフェッチします。

何らかの理由でfetchmanyを使用したい場合は、次のようにすることができます。

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

fetchmanyをこのように使用しても、プリフェッチされたバッチが使い果たされるまで、サーバーへのネットワークコールがトリガーされて行が増えることはありません。 (これは上記のコードでは何も提供しない複雑な例ですが、必要に応じてfetchmanyを使用する方法を示しています。)

25
Demitri

一度に数百万行をロードしたくない場合、私はこのようなことをする傾向があります。何百万もの行をメモリにロードすると、プログラムをかなりのメモリを消費します。特に、これらの行からpythonドメインオブジェクトなどを作成している場合。名前のuuid4が必要かどうかはわかりませんが、私の考えでは2つのプロセスが同じクエリを実行しても、重複しない個々のサーバー側カーソルが必要です。

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サーバーなどでストレージの問題が発生するかどうかを誰かが知っているかどうか、興味があります。

1
nackjicholson