セッション
既定の API は呼び出しごとに新しいデータベースセッションを使います。セッションを自分で開く必要がある場面とその理由を説明します。
既定の動作: 呼び出しごとに新しいセッション
await env.db.exec("select 1");この 1 行で、接続・クエリの実行・同期・切断までをすべて行います。そのため、呼び出しの間にセッションの状態は残りません。
BEGINは次の呼び出しに引き継がれません。SETの設定は次の呼び出しに適用されません。- prepared statement や temporary table も保持されません。
接続を再利用しない理由
接続を再利用するには、直前のリクエストが残した状態を完全に取り除いたという保証が必要です。PostgreSQL の DISCARD ALL がその役割を担いますが、このエンジンですべての状態を安全に消去できるかはまだ確認できていません。
安全性が確認されていない状態で接続を再利用すると、あるリクエストの SET 設定や temporary table が別のユーザーのリクエストに影響しかねません。そのため現在は毎回接続を閉じて開き直しています。リクエストの間でセッションの状態が引き継がれることはありません。
パフォーマンスの特性
| 段階 | コスト |
|---|---|
| 新しいセッションの基本コスト | 2.6 ms |
$1 パラメータ | +0.6 ms |
| 実テーブルの参照(カタログの準備) | +5 ms |
| 書き込み(コミット + flush) | +9 ms |
テーブル参照の 5 ms は、呼び出しごとに新しいセッションを開いてカタログを準備するコストです。安全なセッション再利用が確認できれば短縮できます。PostgreSQL プロトコルで直接接続してセッションを維持した場合、同じクエリは 0.55 ms です。
セッションを直接使う
複数の SQL 文を同じセッションで実行する必要がある場合は、セッションハンドルを使ってください。
const s = await env.db.session();
try {
await s.exec("begin");
await s.exec("insert into a values ($1)", [1]);
await s.exec("insert into b values ($1)", [2]);
await s.exec("commit");
} finally {
await s.close();
}@runlot/pg では pool.connect() が同じ役割を果たします。
ルール
close()は 冪等 です。複数回呼び出しても問題ありません。close()は開いているトランザクションを ロールバック します。コミットするには自分でCOMMITを送る必要があります。- 閉じたハンドルで SQL を実行すると
08003エラーになります。 - SQL 文を実行しないまま 30 秒 が経過すると、セッションを回収して開いているトランザクションをロールバックします。
リクエストの範囲内でのみ使ってください
セッションハンドルをグローバル変数に保存したり、リクエストの外へ渡したりしないでください。30 秒の制限にかかってトランザクションがロールバックされる可能性があり、セッションを保持している間は他のリクエストも待たされます。
同時リクエストは順番に処理されます
プロジェクトごとのデータベースリクエストは一度に 1 件ずつ処理されます。セッションを長く保持するリクエストがあると、他のリクエストは待機します。長いトランザクションは、プロジェクト全体の応答時間を延ばすおそれがあります。
SQL 文 1 つあたりの既定の実行制限は 10 秒です。これを超えたクエリは中断されます。