runlot

トランザクション

1 つのデータベースセッションの中では、通常の PostgreSQL トランザクションを利用できます。セッションが違えば、トランザクションも分かれます。

サポートしている機能

const s = await env.db.session();
try {
  await s.exec("begin");
  await s.exec("update accounts set balance = balance - $1 where id = $2", [100, 1]);
  await s.exec("update accounts set balance = balance + $1 where id = $2", [100, 2]);
  await s.exec("commit");
} catch (e) {
  await s.exec("rollback");
  throw e;
} finally {
  await s.close();
}
  • BEGINCOMMITROLLBACK
  • SAVEPOINT と savepoint のロールバック
  • SET TRANSACTION ISOLATION LEVEL
  • 6 つの ORM のトランザクション API
  • セッション内の SET、temporary table、PREPARE

注意: 呼び出しを分けるとトランザクションも分かれます

env.db.exec を 2 回呼び出した場合、その間で 1 つのトランザクションが維持されることはありません。呼び出しごとに新しいセッションを使うため、BEGIN は次の呼び出しに適用されません。@runlot/pgpool.query() も同様です。トランザクションには pool.connect() を使ってください。

時刻関数

now() はトランザクションが 開始した時刻 を返します。同じトランザクション内で何度呼び出しても値は同じです。現在時刻が必要な場合は clock_timestamp() を使ってください。

トランザクションを長く保持しないでください

プロジェクトごとのデータベースリクエストは一度に 1 件ずつ処理されます。長いトランザクションは、そのプロジェクトの他のリクエストを待たせます。トランザクションの中で外部 API を呼び出す処理は避けてください。

SQL 文を実行しないまま 30 秒が経過すると、セッションを回収して開いているトランザクションをロールバックします。1 つのリクエストの中でセッションを開いて閉じる方法をおすすめします。

エラーコードで分岐する

const r = await s.tryExec("insert into users (email) values ($1)", [email]);
if (!r.ok && r.error.code === "23505") {
  await s.exec("rollback");
  return new Response("すでに登録されているメールアドレスです", { status: 409 });
}

一意性制約違反を P2002 に変換するのは Prisma だけです。他の ORM は SQLSTATE 23505 をそのまま返します。

このページの目次