psycopg3への移行でつまずく箇所はほぼ決まっています。パッケージ名がpsycopgに変わったこと、パラメータの埋め込みがクライアント側からサーバー側へ移ったこと、そしてwith文が接続そのものを閉じるようになったこと。この3つはそれぞれ違う壊れ方をします。パッケージ名はModuleNotFoundError、パラメータ束縛はSETやDDLでの実行時エラー、with文は2周目に閉じた接続をつかむ形で表面化します。ここでは公式が「Differences from psycopg2」として挙げる16項目を、移行作業で実際に踏む順に並べ直し、あわせてpsycopg[binary]とpsycopg[c]の選び分け、接続プールの既定値までを2026年9月時点の一次情報で整理します。
まとめ:移行前に押さえる5点
- パッケージ名は
psycopg。PyPIのpsycopg3は配布ファイルを1件も持たない予約枠なので、pip install psycopg3は必ず失敗する。psycopg2-binaryの置き換え先はpsycopg[binary]、psycopg2の置き換え先はpsycopg[c]。 - パラメータはサーバー側で束縛される。そのため
SET・NOTIFYとDDLでは%sが使えず、IN %sにタプルを渡す書き方も通らない。= ANY(%s)への書き換えかClientCursorへの切り替えで対処する。 with connは接続ごと閉じる。psycopg2ではトランザクションだけがコミットされ接続は生きていた。トランザクションを区切りたいだけならconn.transaction()を使う。- Python 3.10未満ではpsycopg 3.3が入らない。3.3でPython 3.8と3.9のサポートが打ち切られたため、古い実行環境では警告もなくpsycopg 3.2系まで巻き戻る。
- psycopg2は終息していない。公式READMEは「広く使われており現在も保守されているが、新機能が入る予定はない」と明記している。動いている既存システムを急いで書き換える理由にはならない。判断軸は「psycopg3にしかない機能が要るか」で、具体的にはasyncioネイティブの非同期アクセス・動的データの
COPY・静的型検査・パイプラインモードの4つ。なかでも非同期が移行の最大の理由になる。
以下、インストール、16の非互換、接続プール、psycopg3で新設された機能の順に見ていきます。
psycopg3のインストール|正しいパッケージ名とbinary・c・純Pythonの選び分け
pip install psycopg3が失敗する理由とpsycopg[binary]への読み替え
psycopg3のディストリビューション名はpsycopg3ではなくpsycopgです。公式のインストール手順にも「Fun fact: there is no psycopg3 package, only psycopg!」という注記が入っています。PyPIにはpsycopg3という名前のプロジェクト自体は存在しますが、バージョンは0.1、classifierは「Development Status :: 1 – Planning」で、配布ファイルが1件も登録されていません。したがってpip install psycopg3を実行すると次のように落ちます。
$ pip install psycopg3
ERROR: Could not find a version that satisfies the requirement psycopg3 (from versions: none)
ERROR: No matching distribution found for psycopg3
正しい導入コマンドはこちらです。pip自体が古いとバイナリホイールを解決できないため、公式も先にpipの更新を指示しています(要件はpip 20.3以降)。
pip install --upgrade pip
pip install "psycopg[binary]"
binary・c・純Pythonの3択と選定基準
psycopgのextrasはc・binary・pool・test・dev・docsの6種類で、このうちインストール方式を決めるのがbinaryとcです。公式ドキュメントが示す3方式の違いは次のとおりです。
| 方式 | コマンド | 速度 | libpqの事前導入 | ビルドツール | psycopg2での対応物 |
|---|---|---|---|---|---|
| バイナリ | pip install “psycopg[binary]” | 速い | 不要 | 不要 | psycopg2-binary |
| ローカルビルド | pip install “psycopg[c]” | 速い | 必要 | 必要 | psycopg2 |
| 純Python | pip install psycopg | 遅い | 必要 | 不要 | 該当なし |
公式は大多数のユーザーにbinaryを推奨し(表の見出しが “Binary installation (recommended for most users)”)、本番サイトに限ってcを推奨しています(”This is the preferred way to install Psycopg for a production site.”)。理由は、システムのlibpqやlibsslに動的リンクされるため、OS側のセキュリティ更新がそのままpsycopgにも効くからです。バイナリ版はlibpqを同梱するので、OSを更新してもpsycopg内部のライブラリは古いままになります。ただしcにはCコンパイラ、Pythonの開発ヘッダ、libpq-dev、PATH上のpg_configという4つの前提が要ります。ビルドエラーを自力で読めない環境でcを選ぶのは、デプロイのたびに詰まる典型パターン。
選定でもう一つ注意が要るのがApple Siliconです。psycopg 3.1.20以降、ARM64 macOS向けのバイナリパッケージはmacOS 14.0未満に対して提供されなくなりました。13以前のmacOSを使い続けているマシンでは、OSを上げるかローカルビルドか純Pythonに切り替える必要があります。PyPyについてもバイナリ版とローカルビルドはサポート外で、純Pythonのみです。
対応バージョンの範囲とPython 3.9で起きる巻き戻り
psycopg 3.3系の対応範囲は、Pythonが3.10から3.14、PyPyが3.10から3.11、PostgreSQLが10から18です。ここで見落としやすいのがPythonの下限で、公式は「Python 3.8 and 3.9 are supported before Psycopg 3.3」と書いています。つまり3.9以下の環境でもpip install自体は成功しますが、入るのは3.3系ではなく3.2系です。
| 実行環境 | psycopg | psycopg_pool | Cursor.results() |
|---|---|---|---|
| Python 3.14.6 | 3.3.5 | 3.3.1 | あり |
| Python 3.9.6 | 3.2.13 | 3.2.8 | なし |
上の値は、同一のコマンドpip install "psycopg[binary,pool]"を2つのPythonバージョンのvenvで実行し、psycopg.__version__とpsycopg_pool.__version__を読んだ結果です。エラーも警告も出ないまま解決されるバージョンだけが変わるため、「ドキュメントどおりに書いたのにcur.results()が無い」という形で初めて気づくことになります。稼働環境のPythonバージョンは、psycopgのバージョンを確認するときに必ずセットで見てください。
python -c "import sys, psycopg, psycopg_pool; print(sys.version.split()[0], psycopg.__version__, psycopg_pool.__version__)"
PostgreSQL側は10から18が対象ですが、公式が実際にCIで回しているのはサポート期間中のリリースのみで、期限切れのバージョンはベストエフォート扱いです。サーバー側の対応可否を判断する前に、稼働中のPostgreSQLがどの世代なのかをPostgreSQLのバージョン確認方法で確定させておくと手戻りが減ります。
psycopg2とpsycopg3の非互換16項目|移行で実際に踏む順
公式ドキュメントの「Differences from psycopg2」は、末尾の「What’s new in Psycopg 3」(新機能へのリンク集)を除くと16項目を挙げています。全部を暗記する必要はなく、移行作業で先に踏むのは上から数個です。次の表は公式の16項目を移行で踏む順に並べ直したもので、影響範囲が広い項目は下位ケースごとに行を割り、逆に公式で1項目のカーソルサブクラスは置き換え先が3通りあるため3行に展開しています。行数と公式の項目数は一致しません。
| 項目 | psycopg2 | psycopg3 |
|---|---|---|
| パッケージ名 | psycopg2 / psycopg2-binary | psycopg[c] / psycopg[binary] |
| パラメータ束縛 | クライアント側 | サーバー側(拡張問い合わせプロトコル) |
| SET・NOTIFYの%s | 使える | SyntaxError |
| DDLの%s | 使える | UndefinedParameter |
| 拡張問い合わせプロトコル | 不使用 | 既定で使用(PgBouncer管理コンソール等では不可) |
| パラメータ付きの複文 | 使える | 不可(分割かsql.SQL) |
| 複数結果セット | 最後の1つだけ | results()で全件 |
| IN %s にタプル | 使える | = ANY(%s) にリスト |
| IS %s | 使える | IS NOT DISTINCT FROM %s |
| 辞書で受け取る | RealDictCursor | row_factory=dict_row |
| 名前付きタプル | NamedTupleCursor | row_factory=namedtuple_row |
| 位置と名前の両対応 | DictCursor | 相当物なし |
| 型適応 | 旧adaptation | 全面刷新 |
| 可変長関数のキャスト | 推論できる | IndeterminateDatatype(明示キャスト) |
| autocommit時のread_only | 効く | 効かない(GUCで指定) |
| COPY | copy_from / copy_expert | copy() 1本 |
| with conn | トランザクションを閉じる | 接続を閉じる |
| callproc() | あり | 廃止(CALL / SELECT) |
| client_encoding | 属性あり | 廃止(ConnectionInfo.encoding) |
| infinity日付 | date.maxへ写像 | オーバーフロー |
この表で「使える/使えない」が変わる行は、いずれもサーバーサイドバインディングという1つの設計変更から派生しています。そこから見ます。
サーバーサイドバインディングによるSET・NOTIFY・DDLでの%s不可
psycopg2はSQL文字列とパラメータをクライアント側で結合してからサーバーへ送っていました。psycopg3はクエリとパラメータを分けて送り、PostgreSQL側で束縛します。SQLインジェクションの余地が構造的に消える一方、PostgreSQLがパラメータを受け付けない構文では使えなくなりました。SELECTとINSERT・UPDATE・DELETEは問題ありませんが、SETやNOTIFY、それにDDL全般が該当します。落ちたときの例外は同じではありません。SETやNOTIFYはSyntaxErrorですが、CREATE TABLEなどのDDLはUndefinedParameterになります。エラー名でSQLの種類を絞り込めます。
# NG: SET / NOTIFY / DDL ではサーバーサイドバインディングが効かない
conn.execute("SET TimeZone TO %s", ["UTC"])
# psycopg.errors.SyntaxError: syntax error at or near "$1"
# DDL は投げられる例外の種類が違う
conn.execute("CREATE TABLE foo (id int DEFAULT %s)", [42])
# psycopg.errors.UndefinedParameter: there is no parameter $1
# 対処1: 同じことをする関数へ置き換える(SET なら set_config、NOTIFY なら pg_notify)
conn.execute("SELECT set_config('TimeZone', %s, false)", ["UTC"])
conn.execute("SELECT pg_notify(%s, %s)", ["chan", "42"])
# 対処2: sql.SQL でクライアント側に組み立てる
from psycopg import sql
cur.execute(sql.SQL("CREATE TABLE {} (id int DEFAULT {})").format(sql.Identifier("foo"), 42))
# 対処3: ClientCursor を使い psycopg2 と同じ束縛方法に戻す
cur = psycopg.ClientCursor(conn)
cur.execute("CREATE TABLE foo (id int DEFAULT %s)", [42])
対処3のClientCursorは、Connection.cursor_factoryに設定すれば以降のcursor()が既定で返すようになります。移行の初手としてこれを入れると、psycopg2時代のSQLをほぼそのまま動かせます。ただしサーバーサイドバインディングという最大の利点を捨てることになるので、恒久策ではなく段階移行の踏み台として使うのが妥当です。PgBouncerの管理コンソールのように拡張問い合わせプロトコル自体が通らない相手では、逆にClientCursorが正解になります。
同じ理由で、パラメータを渡すときにセミコロン区切りの複文を1回のexecute()に入れられません。パラメータを使わない複文であれば従来どおり通ります。なおCREATE DATABASEのようにトランザクション外でしか実行できない文が複文に混ざると、autocommitを有効にしていてもエラーになります。この制約はpsycopg2でも同じです。
IN %sとIS %sの書き換え先|= ANY()とIS NOT DISTINCT FROM
psycopg2で頻出だった2つの書き方が通らなくなります。どちらも書き換え先はpsycopg2でも動くので、両対応のコードを書く場合はpsycopg3側の形に統一しておくと安全です。
# psycopg2 では書けた形(psycopg3 では SyntaxError)
conn.execute("SELECT * FROM foo WHERE id IN %s", [(10, 20, 30)])
conn.execute("SELECT * FROM foo WHERE field IS %s", [None])
# psycopg3 での書き換え先(psycopg2 でもそのまま動く)
conn.execute("SELECT * FROM foo WHERE id = ANY(%s)", [[10, 20, 30]])
conn.execute("SELECT * FROM foo WHERE field IS NOT DISTINCT FROM %s", [None])
= ANY()への置き換えには副次的な利点が付いてきます。INは空のリストを受け付けませんが、= ANY()は空配列をそのまま渡せる。候補が0件になり得る検索条件なら、psycopg2のままでも= ANY()に寄せておくと分岐が1本減ります。注意が要るのはIS NOT DISTINCT FROMのほう。IS %sの置き換えがIS NOT DISTINCT FROM %s、IS NOT %sの置き換えがIS DISTINCT FROM %sで、NOTの位置が逆転します。
もう1つ、可変長引数の関数で型が決まらずエラーになることがあります。json_build_array(%s, %s)のような呼び出しでIndeterminateDatatypeが出た場合は、%s::textのように明示的なキャストを足せば解決します。
with connectionの意味変更とtransaction()での置き換え
psycopg2のwith conn:はトランザクションのコミット・ロールバックだけを行い、接続は開いたままでした。psycopg3ではwithブロックを抜けた時点で接続そのものが閉じます。ファイルオブジェクトなど他のPythonリソースと同じ挙動に揃えたという変更で、psycopg2の感覚のまま「ループの中でwithブロックを回す」コードを持ち込むと、2周目で接続済みのはずの接続が閉じています。
import psycopg
# with を抜けると「接続」が閉じる(psycopg2 はトランザクションだけ閉じた)
with psycopg.connect("postgresql://user:pass@localhost:5432/appdb") as conn:
with conn.cursor() as cur:
cur.execute("SELECT id, name FROM users WHERE id = %s", (10,))
print(cur.fetchone())
トランザクションだけを区切りたい場合はConnection.transaction()を使います。こちらはネストしたトランザクションにも対応していて、内側のブロックはセーブポイントとして扱われます。psycopg2のwith conn:を機械的に置換するなら、こちらが対応先です。
with psycopg.connect(DSN) as conn:
with conn.transaction():
conn.execute("UPDATE accounts SET balance = balance - %s WHERE id = %s", (100, 1))
conn.execute("UPDATE accounts SET balance = balance + %s WHERE id = %s", (100, 2))
# ここで COMMIT。接続が閉じるのは外側の with を抜けたとき
autocommitを有効にした接続では、read_onlyなどのトランザクション特性属性が効きません。効くのは非autocommit接続の暗黙トランザクションと、transaction()ブロックが作るトランザクションだけです。autocommitのまま読み取り専用にしたいときはSET default_transaction_read_only TO trueを実行します。
RealDictCursor・NamedTupleCursorからrow_factoryへの対応表
psycopg2では取得形式をカーソルのサブクラスで切り替えていました。psycopg3ではカーソルではなく行ファクトリを差し替えます。psycopg.rowsが提供するファクトリはtuple_row・dict_row・namedtuple_row・class_row・args_row・kwargs_row・scalar_rowの7種類です。
from psycopg.rows import dict_row, namedtuple_row
# 接続全体の既定を辞書にする(psycopg2 の RealDictCursor 相当)
conn = psycopg.connect(DSN, row_factory=dict_row)
# カーソル単位でも指定できる(NamedTupleCursor 相当)
cur = conn.cursor(row_factory=namedtuple_row)
移行時に問題になるのはDictCursorです。列番号でも列名でも引ける行を返すこの挙動には、psycopg3に相当物がありません。row[0]とrow["name"]を同じコード内で混在させている箇所は、どちらかに統一する書き換えが必要になります。ここが実質的に一番手のかかる移行作業です。
複数結果セットの取得|psycopg 3.3で追加されたCursor.results()
複数の結果セットの扱いも変わりました。psycopg2は複文を実行しても最後の文の結果しか返しませんでしたが、psycopg3では全部の結果にアクセスできます。psycopg 3.3ではCursor.results()が追加され、それ以前はnextset()を使います。
cur.execute("SELECT 1; SELECT 2")
for _ in cur.results():
print(cur.fetchone())
# (1,)
# (2,)
COPY・callproc()・client_encodingの変更点
psycopg2のcopy_from()やcopy_expert()はファイルライクなオブジェクトを前提にしており、プログラムが動的に生成したデータを流し込むのが面倒でした。psycopg3ではCursor.copy()の1本にまとまり、返ってきたオブジェクトに対してブロック単位でも行単位でも読み書きできます。ファイル依存がなくなったことで、非同期のCOPYも書けるようになりました。
with conn.cursor() as cur:
with cur.copy("COPY users (id, name) FROM STDIN") as copy:
for row in rows:
copy.write_row(row)
cursor.callproc()は実装されていません。PostgreSQLの位置指定パラメータやプロシージャ、集合を返す関数を扱えない中途半端な仕様だったためで、置き換え先は素直にSELECT function_name(...)かCALL procedure_name(...)をexecute()に渡す形です。
client_encoding属性も廃止されました。psycopg3はデータベースのクライアントエンコーディングを見て自動でデコードするため、読むだけならConnectionInfo.encodingを参照します。接続時に指定したいときは接続パラメータのclient_encodingを使います。
最後に日付です。PostgreSQLはPythonのdatetimeより広い範囲を扱え、無限大を表すinfinityという値も持ちます。psycopg2はこれをdate.maxに写像していましたが、infinityと9999-12-31が同じPython値になり区別できない問題がありました。psycopg3は9999年より後の日付を一律オーバーフローとして扱います。infinityを入れた列を読んでいるコードは、ここで例外に変わります。
psycopg 3.3系での接続とクエリ実行の基本形
psycopg.connectとwith文を使った接続の書き方
接続はモジュール直下のpsycopg.connect()で作ります。DSNはpostgresql:で始まる接続URIでも、host=... dbname=...形式のキーワード文字列でも、キーワード引数でも渡せます。接続情報をソースに直書きしないという原則は変わらないので、環境変数か設定ファイルから読み込んでください。libpqが解釈するPGHOST・PGUSER・PGPASSWORDなどの環境変数はpsycopg3でもそのまま効きます。
import os
import psycopg
conn = psycopg.connect(
host=os.environ["PGHOST"],
dbname=os.environ["PGDATABASE"],
user=os.environ["PGUSER"],
password=os.environ["PGPASSWORD"],
)
conn.execute()を直接呼ぶとカーソルを作って返してくれるので、1文だけ流すときはcursor()を経由する必要がありません。取得系のメソッドはfetchone()・fetchmany(size)・fetchall()でpsycopg2と同じです。psycopg 3.3からはカーソルが単なるiterableではなくiteratorになったため、next(cur)という書き方も使えます。
パラメータ渡しとsql.SQLによる識別子の組み立て
プレースホルダは%s(位置指定)と%(name)s(名前指定)の2種類で、これはpsycopg2から変わりません。変わったのは前述のとおり束縛される場所です。値として渡せるのは「シングルクォートで囲まれる位置に来るもの」だけで、テーブル名や列名は渡せません。
識別子を動的に組み立てる必要があるときはpsycopg.sqlモジュールを使います。sql.Identifierは識別子として、それ以外はリテラルとして、それぞれ適切なクォートを付けてくれます。
from psycopg import sql
query = sql.SQL("CREATE TABLE {} (id int DEFAULT {})").format(
sql.Identifier("foo"), 42
)
print(query.as_string())
# CREATE TABLE "foo" (id int DEFAULT 42)
文字列連結やf-stringでSQLを組み立てるのはインジェクションの入口。sql.SQLなら、サーバーへ送る前の文字列をas_string()で確認できるぶんデバッグも楽です。DjangoやSQLAlchemyのようなORM経由でPostgreSQLに繋ぐなら、この層を直接触る機会はほとんどありません。ORM側の設定に落とし込む手順はDjangoとPostgreSQLの接続設定とSQLAlchemyとは?Python ORMの基本と使い方で扱っています。
接続プールpsycopg_poolの既定値と設定の勘所
psycopg2の接続プールはpsycopg2.poolとして本体に同梱され、SimpleConnectionPoolとThreadedConnectionPoolが用意されていました。psycopg3ではプールがpsycopg-poolという別パッケージに分離されています。本体とはバージョン番号の進み方も別で、2026年9月時点でpsycopgが3.3.5、psycopg-poolが3.3.1です。pip install "psycopg[pool]"と書けば依存として一緒に入ります。
from psycopg_pool import ConnectionPool
pool = ConnectionPool(DSN, min_size=4, max_size=20)
with pool:
pool.wait()
with pool.connection() as conn:
conn.execute("SELECT 1")
ConnectionPoolの8引数と既定値
ConnectionPoolの主な引数の既定値は次のとおりです。数値はインストールしたpsycopg_pool 3.3.1のコンストラクタシグネチャから読んだものです。
| 引数 | 既定値 | 意味 |
|---|---|---|
| min_size | 4 | 常時保持する接続数 |
| max_size | None | 上限(Noneならmin_sizeで固定) |
| timeout | 30.0秒 | 接続待ちの上限 |
| max_waiting | 0 | 待機キューの上限(0は無制限) |
| max_lifetime | 3600.0秒 | 1接続の寿命 |
| max_idle | 600.0秒 | min_size超過分を閉じるまでの遊休時間 |
| reconnect_timeout | 300.0秒 | 再接続を諦めるまでの時間 |
| num_workers | 3 | 接続準備のバックグラウンドワーカー数 |
max_sizeがNoneのままだとプールはmin_size本で固定されます。スパイクを吸収したいなら明示的に指定してください。timeoutの30秒はWebリクエストの応答時間としては長すぎることが多く、ここを縮めておかないと「DBが詰まったときにアプリ側のワーカーが全部塞がる」形の障害になります。
pool.wait()による起動時のヘルスチェック
設定ミスに早く気づくにはpool.wait()が有効です。プールを開いただけでは接続の失敗が表面化せず、最初のリクエストがPoolTimeoutで落ちて初めて発覚します。wait()はmin_size本の接続が確立するまでブロックし、時間内に揃わなければ例外を投げるので、起動時のヘルスチェックとして機能します。
open引数の扱いと非同期プールのdeprecation
プールを開くタイミングにも変更が入っています。コンストラクタで開く現在の既定について、公式は「将来のリリースで既定がFalseに変わる可能性がある」ためopen=Trueを明示するよう求めています。非同期プールについてはさらに踏み込んでいて、コンストラクタで開くのはdeprecated扱いです。AsyncConnectionPoolではawait pool.open()を呼ぶか、async withで開いてください。
psycopg3で新規に入った機能|非同期・プリペアド・パイプライン
AsyncConnectionによる非同期アクセスの書き方
psycopg2にもノンブロッキングモード自体はあり、connect()にasync=1を渡す方式が2.2から用意されています。ただし呼び出し側がソケットをポーリングする低水準のAPIで、asyncioへ橋渡しするaiopgは1.4.0(2022年10月)を最後に更新が止まっています。実務でasyncioと組み合わせる選択肢が事実上ないという状態でした。psycopg3はAsyncConnection・AsyncCursor・AsyncConnectionPoolを持ち、同期版とほぼ同じ形のコードをasync/await付きで書けます。FastAPIのようなASGIフレームワークでDBアクセスがイベントループを塞ぐ問題は、これで正面から解けます。
import asyncio
from psycopg_pool import AsyncConnectionPool
async def main():
async with AsyncConnectionPool(DSN, open=False) as pool:
async with pool.connection() as conn:
cur = await conn.execute("SELECT id FROM users WHERE id = %s", (10,))
print(await cur.fetchone())
asyncio.run(main())
open=Falseを渡してasync withで開いているのは、前節のdeprecation警告を避けるためです。同期版と違い非同期プールはコンストラクタで開けなくなる方向なので、新規に書くコードは最初からこの形にしておくのが無難です。非同期化そのものの向き不向きは非同期処理とは?同期処理との違いから実装方式までで整理しています。FastAPIのプロジェクト構成に組み込む場合はFastAPIのベストプラクティスのディレクトリ設計が参考になります。
prepare_threshold=5による自動プリペアドステートメント
psycopg3は同じクエリが繰り返されると自動でプリペアドステートメントに切り替えます。閾値はConnection.connect()のprepare_threshold引数で、既定値は5です。公式の定義は「prepare_threshold回を超えて実行されたあとに自動でprepareされる」で、同一接続で同じSQLを5回実行したあと、6回目の実行時にPREPAREが走ります。パース済みの計画が再利用されるのは7回目からです。
この機能は接続単位で効くため、リクエストごとに接続を作って捨てる構成では一度も発動しません。意味を持つのはプールを併用して接続を使い回す構成だけ。逆に、動的にSQL文字列を組み立てて毎回異なるクエリを投げる設計だと、プリペアド化されないまま管理コストだけが乗る形になります。無効化したいときはprepare_thresholdにNoneを渡してください。なおPgBouncerのようなミドルウェアを挟む場合、psycopg 3.2以降はPgBouncer 1.22以降かつmax_prepared_statementsが0より大きいことが条件です。
パイプラインモードとcopy()でのバルク処理
パイプラインモードは、複数のクエリの結果を待たずにまとめてサーバーへ送る仕組みです。1往復あたりのレイテンシが大きいネットワーク越しの接続で、往復回数そのものを減らします。Connection.pipeline()をコンテキストマネージャとして使います。制約として、パイプラインモード中は複文を実行できません。バイナリ結果を取得する場合も同様です。
大量データの投入ではcopy()を検討してください。公式はCOPYを「データベースへデータを投入する最も効率的な方法の一つ」と位置づけています(INSERTとの具体的な倍率は公式に記載がありません)。初期データのロードやバッチ取り込みが対象です。前述のとおりpsycopg3のcopy()はファイル依存がないので、Pythonのジェネレータから直接流し込む形が書けます。
psycopg2を使い続けてよい場面と移行すべき場面の線引き
psycopg2が使えなくなるという情報が出回っていますが、一次情報はそう言っていません。psycopg2のREADMEにある注記は「The psycopg2 package is still widely used and actively maintained, but it is not expected to receive new features.(psycopg2パッケージは今も広く使われ、活発に保守されているが、新機能が入る予定はない)」です。実際、直近のリリースは2.9.12で2026年4月に公開されており、Python 3.14向けのclassifierも付いています。EOLが宣言されているわけではありません。
したがって判断軸は「サポートが切れるか」ではなく「psycopg3の新機能が要るか」です。次のいずれかに当てはまるなら移行する価値があります。
- asyncioで非同期にDBへアクセスしたい。psycopg2側は
async=1の低水準APIと更新の止まったaiopgしかなく、これが移行の最大の理由になる。 - 動的に生成したデータを
COPYで流し込みたい。psycopg2のファイルベースAPIでは中間ファイルかバッファのラッパーが要る。 - mypyなどで静的型検査を通したい。psycopg3は型ヒント付きで設計されている。
- ネットワークレイテンシが支配的な環境で、パイプラインモードにより往復回数を削りたい。
逆に、同期のWebアプリケーションでSELECTとINSERTを素直に投げているだけなら、移行を急ぐ理由はありません。とくにDictCursorを広く使っている既存コードは、置き換え先が存在しないぶん書き換え量が読みにくく、費用対効果が悪くなります。新規プロジェクトはpsycopg3から始め、既存システムはPython本体のバージョンアップやフレームワークの更新と同じタイミングでまとめて移行する、という切り分けが現実的です。前者はpsycopg2のREADME自身が「Psycopg 3 is the evolution of psycopg2 and is where new features are being developed: if you are starting a new project you should probably start from 3!」と勧めているとおりです。
両方を同時に扱う必要がある期間は、psycopg2でも動く書き方(= ANY()、IS NOT DISTINCT FROM、明示的なconn.commit()とconn.close())に先に寄せておくと、実際の切り替え時の差分が小さくなります。
よくある質問
pip install psycopg3 でインストールできますか?
できません。PyPI上のpsycopg3はバージョン0.1の予約枠で配布ファイルが1件もないため、No matching distribution found for psycopg3で失敗します。正しくはpip install "psycopg[binary]"です。公式のインストール手順にも「there is no psycopg3 package, only psycopg」と明記されています。
psycopg[binary] と psycopg2-binary はどう違いますか?
役割は同じで、Cライブラリを同梱した事前ビルド版という位置づけです。違いはパッケージ構成にあります。psycopg2ではpsycopg2とpsycopg2-binaryが同じPythonパッケージをインストールするため、公式の言い方で「依存関係の管理が複雑になり、両方を入れると問題が起きる」状態でした。psycopg3ではインターフェースのpsycopgが本体で、[binary]や[c]は高速化用のextrasとして分かれています。なおpsycopg[binary]と書いたときに実際に入るディストリビューションの名前はpsycopg-binaryで、これは本体と同じ3.3.5です。
psycopg2 はいつまで使えますか?
終了日は公表されていません。psycopg2のREADMEは「広く使われており活発に保守されているが、新機能が入る予定はない」と述べており、直近では2026年4月に2.9.12がリリースされています。バグ修正と新しいPythonへの追随は続く一方、新機能はpsycopg3側にのみ入ります。
psycopg3 の接続プールはどのパッケージにありますか?
psycopg-poolという別パッケージです。psycopg2ではpsycopg2.poolとして本体に含まれていましたが、psycopg3では分離され、バージョンも独立して進みます(2026年9月時点で本体3.3.5に対しプールは3.3.1)。pip install "psycopg[pool]"でまとめて導入でき、from psycopg_pool import ConnectionPoolで読み込みます。
psycopg[c] のインストールがビルドで失敗するのはなぜですか?
[c]はソースからC拡張をビルドするため、4つの前提が揃っている必要があります。Cコンパイラ、Pythonの開発ヘッダ(python3-devなど)、PostgreSQLクライアントの開発ヘッダ(libpq-devなど)、そしてPATH上のpg_configです。公式は「拡張のビルドエラーを自力で切り分けられないなら[c]を試さずバイナリ版を使うこと」と明記しているので、CIやコンテナで詰まるようならpsycopg[binary]へ切り替えるのが早道です。
psycopg3 で asyncio を使うときに必要な設定は?
追加パッケージは不要で、AsyncConnectionとAsyncConnectionPoolを使うだけです。ただし非同期プールをコンストラクタで開くのはdeprecatedになっているため、AsyncConnectionPool(DSN, open=False)をasync withで開くか、await pool.open()を明示的に呼んでください。