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のポイントは何ですか?
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
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
を使用する方法を示しています。)
一度に数百万行をロードしたくない場合、私はこのようなことをする傾向があります。何百万もの行をメモリにロードすると、プログラムをかなりのメモリを消費します。特に、これらの行から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サーバーなどでストレージの問題が発生するかどうかを誰かが知っているかどうか、興味があります。