PostgreSQL 25005 오류 원인과 해결 방법 완벽 가이드

25005
2026년 06월 24일 | DBMS Error 가이드

이 글에서 다루는 내용

25005 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.

25005 no active sql transaction for branch transaction 는?

PostgreSQL 에러 코드 25005는 “no active sql transaction for branch transaction” 으로, 분산 트랜잭션(branch transaction) 또는 서브트랜잭션을 시작하려 할 때 현재 활성화된 SQL 트랜잭션이 존재하지 않을 경우 발생합니다. 쉽게 말해, 트랜잭션 블록 외부에서 SAVEPOINT, RELEASE SAVEPOINT, 또는 ROLLBACK TO SAVEPOINT 같은 명령을 실행하거나, XA(eXtended Architecture) 분산 트랜잭션 컨텍스트에서 트랜잭션이 올바르게 시작되지 않은 상태에서 브랜치 트랜잭션을 조작하려 할 때 나타납니다. 이 에러는 특히 Java의 JTA(Java Transaction API), Python의 SQLAlchemy, 또는 다중 데이터베이스 연결을 사용하는 미들웨어 환경에서 자주 목격됩니다.


주요 발생 원인

1. 활성 트랜잭션 없이 SAVEPOINT 명령 사용

가장 흔한 원인은 BEGIN 또는 START TRANSACTION 없이 곧바로 SAVEPOINT 관련 명령을 실행하는 경우입니다. PostgreSQL에서 SAVEPOINT는 반드시 명시적인 트랜잭션 블록 내에서만 사용할 수 있으며, 오토커밋(autocommit) 모드가 활성화된 상태에서는 각 SQL 문이 독립적인 트랜잭션으로 처리되기 때문에 서브트랜잭션을 생성할 수 없습니다. 애플리케이션 레이어에서 트랜잭션 관리가 느슨하게 구현된 경우 이 문제가 반복적으로 발생합니다.

2. 분산 트랜잭션(XA Transaction) 컨텍스트에서의 순서 오류

XA 분산 트랜잭션을 사용하는 환경(예: Java EE/Jakarta EE의 JTA, Python의 cx_Oracle XA 등)에서 XA START 이전에 브랜치 작업을 수행하거나, XA END 이후 트랜잭션이 이미 종료된 상태에서 추가 SQL을 실행하려 할 때 이 에러가 발생합니다. 분산 트랜잭션 프로토콜(2PC, Two-Phase Commit)의 각 단계를 정확한 순서로 호출하지 않으면 PostgreSQL은 유효하지 않은 트랜잭션 상태로 간주하고 에러를 반환합니다. 이는 복잡한 마이크로서비스 아키텍처에서 트랜잭션 코디네이터와 PostgreSQL 간의 상태 불일치로 이어질 수 있습니다.

3. 커넥션 풀링 환경에서의 트랜잭션 상태 오염

PgBouncer, HikariCP, c3p0 같은 커넥션 풀을 사용할 때, 이전 세션에서 트랜잭션이 완전히 커밋 또는 롤백되지 않은 채로 커넥션이 반환되고, 새 요청이 해당 커넥션을 재사용하면서 발생합니다. 커넥션이 transaction 모드의 풀링을 사용하는 경우, 특히 트랜잭션 경계가 모호한 ORM(Object-Relational Mapping) 프레임워크와 결합될 때 에러 25005가 예기치 않게 발생할 수 있습니다. 이 경우 트랜잭션 상태 초기화 없이 SAVEPOINT를 설정하려는 시도가 에러를 유발합니다.


해결 방법

원인 1 해결: 명시적 트랜잭션 블록 추가

SAVEPOINT를 사용하기 전 반드시 BEGIN으로 트랜잭션을 시작해야 합니다.

-- ❌ 잘못된 사용 (autocommit 상태에서 SAVEPOINT 직접 호출)
SAVEPOINT my_savepoint;
INSERT INTO orders (product_id, quantity) VALUES (101, 5);
RELEASE SAVEPOINT my_savepoint;

-- ✅ 올바른 사용 (BEGIN으로 트랜잭션 블록 내에서 사용)
BEGIN;
  INSERT INTO orders (product_id, quantity) VALUES (101, 5);
  SAVEPOINT my_savepoint;

  INSERT INTO order_details (order_id, detail) VALUES (1, 'Express');
  -- 이 부분에서 오류가 발생하면 SAVEPOINT로 롤백
  ROLLBACK TO SAVEPOINT my_savepoint;

  -- 계속 작업 가능
  INSERT INTO order_details (order_id, detail) VALUES (1, 'Standard');
  RELEASE SAVEPOINT my_savepoint;
COMMIT;

애플리케이션 코드에서 트랜잭션 시작 여부를 확인하는 방법:

-- 현재 트랜잭션 상태 확인
SELECT pg_current_xact_id_if_assigned() AS current_txn_id;
-- NULL이 반환되면 활성 트랜잭션이 없는 상태

원인 2 해결: XA 트랜잭션 순서 교정

PostgreSQL에서 Two-Phase Commit(2PC)을 사용하는 경우, 반드시 아래 순서를 따라야 합니다.

-- ✅ 올바른 XA/2PC 트랜잭션 순서

-- 1단계: 트랜잭션 시작
BEGIN;

-- 2단계: 작업 수행
INSERT INTO accounts (user_id, balance) VALUES (1001, 5000.00);
UPDATE inventory SET stock = stock - 1 WHERE product_id = 42;

-- 3단계: PREPARE (2PC 첫 번째 단계 - 커밋 준비)
PREPARE TRANSACTION 'txn_branch_001';

-- 4단계: 다른 데이터베이스/리소스도 준비 완료 확인 후 최종 커밋
COMMIT PREPARED 'txn_branch_001';

-- 만약 문제가 있다면 롤백
-- ROLLBACK PREPARED 'txn_branch_001';

준비된 트랜잭션 목록 확인:

-- 현재 PREPARED 상태로 대기 중인 트랜잭션 확인
SELECT gid, prepared, owner, database
FROM pg_prepared_xacts
ORDER BY prepared;

오래된 PREPARED 트랜잭션 정리 (DBA 작업):

-- 1시간 이상 오래된 준비된 트랜잭션 롤백 (위험! 신중히 사용)
DO $$
DECLARE
    r RECORD;
BEGIN
    FOR r IN
        SELECT gid FROM pg_prepared_xacts
        WHERE prepared < NOW() - INTERVAL '1 hour'
    LOOP
        EXECUTE 'ROLLBACK PREPARED ' || quote_literal(r.gid);
        RAISE NOTICE 'Rolled back stale prepared transaction: %', r.gid;
    END LOOP;
END;
$$;

원인 3 해결: 커넥션 풀 상태 초기화

커넥션 풀에서 커넥션을 반환받을 때 상태를 명시적으로 초기화합니다.

-- 커넥션 재사용 전 상태 초기화 (PgBouncer server_reset_query 설정과 유사)
DISCARD ALL;

-- 또는 트랜잭션 상태만 초기화
ROLLBACK;  -- 열려 있는 트랜잭션이 있으면 롤백
RESET ALL; -- 세션 파라미터 초기화

PgBouncer 설정 파일 (pgbouncer.ini) 예시:

; 커넥션 반환 시 서버 상태 초기화 쿼리 설정
server_reset_query = DISCARD ALL
; 트랜잭션 모드 사용 시 (SAVEPOINT 미지원으로 주의 필요)
pool_mode = session

애플리케이션 레벨에서 트랜잭션 상태 확인 후 안전하게 시작:

-- 현재 트랜잭션 격리 수준 및 상태 확인 함수
CREATE OR REPLACE FUNCTION safe_begin_transaction()
RETURNS void AS $$
BEGIN
    -- 이미 트랜잭션이 활성화되어 있는지 확인
    IF pg_current_xact_id_if_assigned() IS NOT NULL THEN
        RAISE NOTICE '이미 활성 트랜잭션이 존재합니다. 기존 트랜잭션을 계속 사용합니다.';
    ELSE
        -- 새 트랜잭션 시작 (함수 내에서는 자동으로 트랜잭션 블록에 포함됨)
        RAISE NOTICE '새 트랜잭션을 시작합니다.';
    END IF;
END;
$$ LANGUAGE plpgsql;

예방 방법

1. 트랜잭션 관리 래퍼 패턴 구현

애플리케이션 코드에서 트랜잭션 시작, 커밋, 롤백을 반드시 래퍼(Wrapper) 함수나 클래스를 통해 일관되게 처리하고, SAVEPOINT 사용 전 트랜잭션 활성 상태를 자동으로 검증하는 로직을 포함시키세요. Python의 context manager, Java의 @Transactional 어노테이션처럼 트랜잭션 생명주기를 프레임워크 수준에서 자동 관리하는 방식을 채택하면 개발자 실수로 인한 이 에러를 90% 이상 예방할 수 있습니다.

-- PostgreSQL 저장 프로시저로 안전한 트랜잭션 패턴 구현 예시
CREATE OR REPLACE PROCEDURE process_order_safely(
    p_user_id INT,
    p_product_id INT,
    p_quantity INT
)
LANGUAGE plpgsql AS $$
BEGIN
    -- 트랜잭션은 CALL 문으로 자동 관리됨
    INSERT INTO orders (user_id, created_at) VALUES (p_user_id, NOW());

    SAVEPOINT before_inventory;

    UPDATE inventory
    SET stock = stock - p_quantity
    WHERE product_id = p_product_id AND stock >= p_quantity;

    IF NOT FOUND THEN
        ROLLBACK TO SAVEPOINT before_inventory;
        RAISE EXCEPTION '재고 부족: product_id=%', p_product_id;
    END IF;

    RELEASE SAVEPOINT before_inventory;
    -- CALL 종료 시 자동 커밋
END;
$$;

2. 모니터링 및 로깅 강화

log_min_error_statement = ERROR 설정과 함께 애플리케이션 미들웨어에서 트랜잭션 상태 변화를 추적하는 로깅을 활성화하세요. PostgreSQL의 pg_stat_activity 뷰를 주기적으로 모니터링하여 idle in transaction 상태로 장시간 머물거나, 비정상적으로 오래된 PREPARED 트랜잭션이 누적되는지 확인하는 자동화 알림 시스템을 구축하면 문제를 사전에 감지할 수 있습니다.

-- 비정상 트랜잭션 상태 모니터링 쿼리
SELECT
    pid,
    usename,
    application_name,
    state,
    wait_event_type,
    wait_event,
    EXTRACT(EPOCH FROM (NOW() - xact_start)) AS txn_duration_sec,
    LEFT(query, 100) AS current_query
FROM pg_stat_activity
WHERE state IN ('idle in transaction', 'idle in transaction (aborted)')
   OR (state = 'active' AND xact_start < NOW() - INTERVAL '5 minutes')
ORDER BY txn_duration_sec DESC NULLS LAST;

관련 에러

  • 25000 (invalid_transaction_state): 트랜잭션 상태가 현재 명령을 수행하기에 유효하지 않을 때 발생하는 상위 클래스 에러입니다.
  • 25001 (active_sql_transaction): SET TRANSACTION ISOLATION LEVEL 같은 명령을 이미 시작된 트랜잭션 중간에 실행하려 할 때 발생합니다.
  • 25006 (read_only_sql_transaction): 읽기 전용 트랜잭션에서 데이터 변경(INSERT/UPDATE/DELETE)을 시도할 때 발생하며, 트랜잭션 모드 설정 오류와 함께 자주 등장합니다.
  • 25P01 (no_active_sql_transaction): 활성 트랜잭션 없이 COMMIT 또는 ROLLBACK을 실행할 때 발생하며, 25005와 유사한 맥락에서 트랜잭션 관리 실수로 인해 함께 나타나는 경우가 많습니다.
  • 40001 (serialization_failure): 직렬화 격리 수준에서 트랜잭션 충돌 시 발생하며, 재시도 로직 구현 시 25005 에러 처리와 함께 고려해야 합니다.

DBMS 에러 코드 시리즈

주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.

본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.

댓글 남기기