2026년 06월 25일 | DBMS Error 가이드
이 글에서 다루는 내용
25P01 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
25P01 no active sql transaction 는?
PostgreSQL 에러 코드 25P01 (no active sql transaction)은 트랜잭션이 활성화되어 있지 않은 상태에서 트랜잭션 관련 명령어를 실행하려 할 때 발생하는 에러입니다. 예를 들어, BEGIN 없이 ROLLBACK 또는 COMMIT을 실행하거나, 이미 종료된 트랜잭션 블록 내에서 추가 명령을 수행하려 할 때 이 에러를 마주칩니다. 특히 애플리케이션 레이어에서 트랜잭션 상태를 잘못 관리하거나, 자동 커밋(autocommit) 모드 설정을 혼용하는 경우에 빈번하게 발생합니다.
주요 발생 원인
- 트랜잭션 없이 ROLLBACK 또는 COMMIT 실행
가장 흔한 원인으로, BEGIN 또는 START TRANSACTION 없이 ROLLBACK이나 COMMIT을 직접 호출하는 경우입니다. PostgreSQL은 기본적으로 각 SQL 문을 개별 트랜잭션으로 자동 처리(autocommit)하기 때문에, 명시적인 트랜잭션 블록 없이 ROLLBACK을 실행하면 활성 트랜잭션이 존재하지 않는다는 에러를 반환합니다. 이는 스크립트를 자동화하거나 마이그레이션 도구를 사용할 때 특히 자주 발생합니다.
- 예외 처리 로직의 잘못된 트랜잭션 종료 순서
PL/pgSQL 또는 애플리케이션 코드에서 예외(Exception)가 발생했을 때, 이미 내부적으로 트랜잭션이 중단(aborted) 상태로 전환된 후 다시 ROLLBACK을 호출하거나, 트랜잭션이 전혀 시작되지 않은 상태에서 예외 핸들러가 ROLLBACK을 시도하는 경우입니다. 특히 ORM(Object-Relational Mapping) 프레임워크에서 커넥션 풀링과 함께 사용할 때 이런 상황이 발생하기 쉽습니다.
- 커넥션 풀에서 재사용된 커넥션의 트랜잭션 상태 불일치
PgBouncer나 애플리케이션 레벨 커넥션 풀을 사용할 때, 이전 세션에서 트랜잭션이 제대로 정리되지 않은 커넥션을 재사용하면서 트랜잭션 상태가 불일치하는 경우입니다. 특히 transaction 풀링 모드가 아닌 session 모드에서 세션 상태가 초기화되지 않으면 이 문제가 발생할 수 있으며, 디버깅이 매우 까다롭습니다.
해결 방법
원인 1 해결: 트랜잭션 블록 명시적으로 감싸기
ROLLBACK이나 COMMIT을 사용하기 전에 반드시 BEGIN으로 트랜잭션을 시작해야 합니다.
-- 잘못된 예 (25P01 에러 발생)
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
ROLLBACK; -- ERROR: 25P01 no active sql transaction
-- 올바른 예
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;
COMMIT;
-- 에러가 발생했을 때 안전하게 처리하는 예
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
-- 에러 발생 시
ROLLBACK;
현재 트랜잭션 상태를 확인하려면 아래 쿼리를 사용하세요.
-- 현재 트랜잭션 상태 확인
SELECT pg_current_xact_id_if_assigned();
-- 세션의 트랜잭션 상태 확인
SELECT state, query
FROM pg_stat_activity
WHERE pid = pg_backend_pid();
원인 2 해결: PL/pgSQL에서 안전한 예외 처리
PL/pgSQL 내에서는 EXCEPTION 블록을 올바르게 사용하여 트랜잭션 상태를 관리해야 합니다.
-- 잘못된 예: 함수 외부에서 트랜잭션 없이 ROLLBACK 시도
DO $$
BEGIN
INSERT INTO orders (order_id, amount) VALUES (1001, 500);
-- 어딘가에서 에러 발생
EXCEPTION
WHEN OTHERS THEN
-- PL/pgSQL 블록 내 ROLLBACK은 불가
RAISE NOTICE '에러 발생: %', SQLERRM;
END;
$$;
-- 올바른 예: 외부 트랜잭션 블록과 함께 사용
BEGIN;
DO $$
BEGIN
INSERT INTO orders (order_id, amount) VALUES (1001, 500);
INSERT INTO order_items (order_id, product_id, qty) VALUES (1001, 42, 3);
EXCEPTION
WHEN unique_violation THEN
RAISE NOTICE '중복 주문 감지: order_id=1001';
WHEN OTHERS THEN
RAISE EXCEPTION '예상치 못한 에러: %', SQLERRM;
END;
$$;
COMMIT;
원인 3 해결: 커넥션 풀 사용 시 트랜잭션 상태 초기화
커넥션을 반환하기 전에 항상 트랜잭션 상태를 초기화하고, 트랜잭션 상태를 명시적으로 확인하는 습관을 들여야 합니다.
-- 커넥션 재사용 전 상태 확인 및 정리
-- 트랜잭션이 활성 상태인지 확인
SELECT CASE
WHEN pg_current_xact_id_if_assigned() IS NOT NULL
THEN '활성 트랜잭션 있음'
ELSE '활성 트랜잭션 없음'
END AS transaction_status;
-- 커넥션 풀에 반환하기 전 안전하게 트랜잭션 정리
-- (애플리케이션 레벨에서 수행)
DISCARD ALL; -- 세션 상태 전체 초기화 (커넥션 풀 반환 시 권장)
-- 또는 트랜잭션만 종료
ROLLBACK; -- 활성 트랜잭션이 있을 경우에만 유효
-- PgBouncer 사용 시 server_reset_query 설정 예시 (postgresql.conf 수준)
-- server_reset_query = DISCARD ALL
-- 애플리케이션에서 조건부 ROLLBACK 패턴 (psql 스크립트 활용)
\set ON_ERROR_STOP on
BEGIN;
INSERT INTO payments (payment_id, amount, status)
VALUES (9001, 250.00, 'pending');
UPDATE wallets
SET balance = balance - 250.00
WHERE wallet_id = 10;
-- 잔액 음수 방지 체크
DO $$
DECLARE
current_balance NUMERIC;
BEGIN
SELECT balance INTO current_balance FROM wallets WHERE wallet_id = 10;
IF current_balance < 0 THEN
RAISE EXCEPTION '잔액 부족: 현재 잔액 %', current_balance;
END IF;
END;
$$;
COMMIT;
예방 방법
- 트랜잭션 상태를 항상 명시적으로 관리하고, 애플리케이션 레이어에서 트랜잭션 래퍼 패턴을 도입하세요.
모든 DB 접근 코드는 트랜잭션의 시작(BEGIN)과 종료(COMMIT / ROLLBACK)를 명확하게 쌍으로 처리하는 래퍼(wrapper) 함수나 컨텍스트 매니저를 통해 실행되도록 구조화하는 것이 Best Practice입니다. Python의 경우 with connection 구문, Java의 경우 Spring의 @Transactional 어노테이션처럼 트랜잭션 생명주기를 프레임워크에 위임하면 25P01과 같은 에러를 사전에 방지할 수 있습니다. 또한, 스크립트 작성 시 \set ON_ERROR_STOP on을 psql 옵션으로 설정하면 에러 발생 즉시 스크립트가 중단되어 잘못된 트랜잭션 명령이 연쇄 실행되는 것을 막을 수 있습니다.
- 커넥션 풀 설정 시
DISCARD ALL또는RESET ALL을 서버 리셋 쿼리로 지정하세요.
PgBouncer, pgpool-II 등 커넥션 풀러를 사용하는 환경에서는 반드시 server_reset_query = DISCARD ALL 설정을 통해 커넥션이 풀로 반환될 때마다 세션 상태(트랜잭션, 임시 테이블, 세션 변수 등)를 완전히 초기화하도록 설정해야 합니다. 이 설정이 누락되면 이전 세션의 트랜잭션 잔재가 다음 사용자에게 그대로 전달되어 예측 불가능한 에러를 야기합니다. 또한, 모니터링 시스템(예: pg_stat_activity 기반 대시보드)을 통해 idle in transaction 상태로 오래 머무는 커넥션을 주기적으로 감지하고 강제 종료하는 정책을 수립하세요.
관련 에러
- 25001 (active sql transaction): 트랜잭션이 이미 활성화된 상태에서
BEGIN을 중첩 실행할 때 발생하는 경고. 25P01과 반대 상황입니다. - 25P02 (in failed sql transaction): 트랜잭션 내에서 에러가 발생한 후, 해당 트랜잭션이 중단(aborted) 상태임에도 추가 쿼리를 실행하려 할 때 나타납니다.
ROLLBACK후 새 트랜잭션을 시작해야 합니다. - 40001 (serialization failure): 트랜잭션 격리 수준 충돌로 인한 직렬화 실패로, 재시도 로직이 없을 경우 25P01 에러로 이어질 수 있습니다.
- 57P01 (admin shutdown): 서버 재시작으로 인한 강제 세션 종료로, 트랜잭션이 예기치 않게 종료된 후 애플리케이션이 ROLLBACK을 재시도할 때 25P01이 발생할 수 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.