runlot

트랜잭션

하나의 데이터베이스 세션 안에서는 일반적인 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();
}
  • BEGIN, COMMIT, ROLLBACK
  • SAVEPOINT와 savepoint 롤백
  • SET TRANSACTION ISOLATION LEVEL
  • 여섯 ORM의 트랜잭션 API
  • 세션 안의 SET, temporary table, PREPARE

주의: 호출을 나누면 트랜잭션도 나뉩니다

env.db.exec을 두 번 호출한 사이에는 하나의 트랜잭션이 유지되지 않습니다. 호출마다 새 세션을 사용하므로 BEGIN이 다음 호출에 적용되지 않습니다. @runlot/pgpool.query()도 같습니다. 트랜잭션에는 pool.connect()를 사용하세요.

시간 함수

now()는 트랜잭션이 시작한 시각을 반환합니다. 같은 트랜잭션에서 여러 번 호출해도 값은 같습니다. 현재 시각이 필요하면 clock_timestamp()를 사용하세요.

트랜잭션을 길게 유지하지 마세요

프로젝트당 데이터베이스 요청은 한 번에 하나씩 처리됩니다. 긴 트랜잭션은 프로젝트의 다른 요청을 대기시킵니다. 트랜잭션 안에서 외부 API를 호출하는 작업은 피하세요.

SQL 문을 실행하지 않은 채 30초가 지나면 세션을 회수하고 열린 트랜잭션을 롤백합니다. 하나의 요청 안에서 세션을 열고 닫는 방식을 권장합니다.

오류 코드로 분기하기

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 });
}

Prisma만 고유성 제약 조건 위반을 P2002로 변환합니다. 나머지 ORM은 SQLSTATE 23505를 그대로 반환합니다.

이 페이지의 목차