PythonからPostgreSQLを操作するとき、psycopg2とasyncpgでは、データベースへの問い合わせ方と待ち時間の扱いが異なります。
同期処理を前提とする既存アプリケーションではpsycopg2が扱いやすく、asyncioを使うアプリケーションでデータベース待ちの間にも別の処理を進めたい場合はasyncpgが候補になります。ただし、非同期であることだけを理由に、すべてのクエリが速くなるわけではありません。実際の選択では、アプリケーション全体の実行方式、利用するフレームワーク、同時処理の量、接続管理を合わせて考えます。
psycopg2とasyncpgの比較
| 比較項目 | psycopg2 | asyncpg |
|---|---|---|
| 基本の実行方式 | 同期処理 | asyncioを使う非同期処理 |
| 呼び出し方 | 通常の関数呼び出し | asyncとawaitを使用 |
| SQLの値の指定 | %s |
$1、$2のような番号付き |
| トランザクション | commit()とrollback()で明示的に終了 |
async with connection.transaction()で範囲を表現できる |
| 合わせやすい構成 | 同期型の処理や既存コード | asyncioを採用した非同期アプリケーション |
この表は設計上の違いを示すものであり、実測性能の順位ではありません。処理時間はSQL、データ量、インデックス、ネットワーク、接続の再利用方法などにも左右されます。
同期処理と非同期処理の違い
同期処理では、データベースから結果が返るまで、その処理の流れは次へ進みません。この待ち方をブロッキングI/Oと呼びます。コードを上から順に追いやすく、バッチ処理や管理用ツールのように同時処理が少ない用途では、構成を簡潔に保ちやすい方法です。
非同期処理では、データベースの応答を待つ間に、イベントループが別の処理へ実行機会を渡します。多数のリクエストが同時に発生し、それぞれがネットワークやデータベースの応答を待つ構成では、待ち時間を使って別の仕事を進められます。
ただし、非同期処理はデータベース側のSQL実行そのものを短縮する仕組みではありません。非同期の利点は、主に複数の待ち時間を重ねて扱えることにあります。CPU負荷の高い処理や遅いSQLは、別途見直す必要があります。
psycopg2の特徴
psycopg2は、PythonからPostgreSQLへ接続するデータベースアダプターです。通常の同期APIを使う場合は、接続を作り、カーソルでSQLを実行し、結果を受け取るという順序がコードにそのまま現れます。
psycopg2が合う場面
- アプリケーション全体が同期処理で構成されている
- 既存コードや利用中のフレームワークがpsycopg2を前提としている
- 同時処理の多さより、実装の単純さと既存資産との整合を優先する
導入時に確認したい点
同期APIでデータベースの応答を待っている間は、その実行の流れが止まります。同時リクエストを処理するアプリケーションでは、スレッドやプロセスを含めた並行処理の設計が必要です。
「同期処理だから高負荷用途には使えない」と一律に判断することはできません。必要な同時処理量を満たせるか、接続数と応答時間を測って判断します。
asyncpgの特徴
asyncpgは、Pythonのasyncioを前提とするPostgreSQLクライアントです。接続、SQL実行、結果取得をawaitするため、待機中にイベントループがほかのタスクを進められます。
asyncpgが合う場面
- アプリケーション全体が
asyncioを使っている - データベースや外部サービスへのI/O待ちが重なる
- 非同期の接続管理と例外処理をチームで運用できる
FastAPIを含む構成でも、フレームワーク名だけで採用を決めるのは早計です。エンドポイントからデータアクセス層まで非同期でつなぐのか、同期コードをどこに置くのかを先に決めます。API全体の判断項目は、PythonとFastAPIで業務APIを作る前に決めることでも確認できます。
導入時に確認したい点
asyncとawaitを付けるだけでは、アプリケーション全体が非同期になるわけではありません。同じ処理経路に長時間の同期I/Oが残ると、イベントループを止める原因になります。データベースアクセスだけでなく、その前後の処理も確認します。
性能を比較するときの考え方
性能比較では、一回のクエリが返るまでの時間と、一定時間に処理できるリクエスト数を分けます。前者はSQLやデータベース設計の影響を強く受け、後者は待ち時間の扱い、接続数、アプリケーションの並行処理方式にも影響されます。
asyncpgは、複数のI/O待ちが重なる非同期アプリケーションで処理を組み立てやすい一方、psycopg2でも要件に合う並行処理と接続管理を設計すれば運用できます。ライブラリ名だけを変えたベンチマークではなく、次の条件をそろえて測ります。
- 同じSQLと同じデータ
- 同じインデックスとPostgreSQL設定
- 同じ接続数と接続の再利用条件
- 実運用に近い同時リクエスト数
- 平均値だけでなく、遅い応答を含む分布
トランザクションの書き方
トランザクションとは、複数のデータベース操作を一つの処理単位として扱う仕組みです。途中で失敗した場合は変更を取り消し、すべて成功した場合だけ確定させます。
psycopg2の例
psycopg2では、成功時にcommit()、失敗時にrollback()を呼ぶ流れを明示できます。
import psycopg2
conn = psycopg2.connect(dsn)
try:
with conn.cursor() as cur:
cur.execute(
"INSERT INTO sample_table (col) VALUES (%s)",
(value,),
)
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
例外を再送出することで、呼び出し元も失敗を検知できます。ロールバックだけを行って例外を握りつぶすと、処理が成功したように見えるため注意が必要です。
asyncpgの例
asyncpgでは、トランザクションに含める処理を非同期コンテキストマネージャーで囲めます。
import asyncio
import asyncpg
async def main():
conn = await asyncpg.connect(dsn)
try:
async with conn.transaction():
await conn.execute(
"INSERT INTO sample_table (col) VALUES ($1)",
value,
)
finally:
await conn.close()
asyncio.run(main())
両方の例で値をSQL文字列へ直接埋め込まず、ライブラリが用意するプレースホルダーを使っています。違いは、psycopg2が%s、asyncpgが$1形式である点です。
選択前のチェックリスト
- 実行方式:アプリケーションは同期処理か、
asyncioによる非同期処理か - 既存資産:フレームワーク、ライブラリ、テスト、運用手順はどちらを前提としているか
- 同時処理:ピーク時にどれだけのリクエストとデータベース接続を扱うか
- 処理内容:待ち時間が中心か、CPU処理や重いSQLが中心か
- 保守性:チームが非同期コードの例外処理と接続管理を継続して扱えるか
- 実測:本番に近い条件で必要な応答時間と処理量を満たすか
選び方の結論
同期処理を中心とする既存アプリケーションや、小規模なバッチと管理ツールでは、psycopg2の単純な実行モデルが適しています。アプリケーション全体をasyncioで構成し、多数のI/O待ちを並行して扱う場合は、asyncpgが候補になります。
最終判断は「どちらが常に速いか」ではなく、「どちらが現在の実行方式と運用条件に合うか」で行います。候補を絞った後は、同じSQLと同じ負荷条件で測定し、性能と保守性の両方を確認してください。

