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

3B000
2026년 09월 04일 | DBMS Error 가이드

이 글에서 다루는 내용

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

3B000 savepoint exception 는?

PostgreSQL 에러 코드 3B000 savepoint exception은 트랜잭션 내에서 세이브포인트(SAVEPOINT)를 잘못 사용하거나, 존재하지 않는 세이브포인트를 참조하려 할 때 발생하는 에러입니다. 세이브포인트는 트랜잭션의 중간 지점을 표시하여 부분 롤백을 가능하게 해주는 기능으로, 복잡한 데이터 처리 로직에서 매우 빈번하게 활용됩니다. 이 에러는 주로 애플리케이션 레이어에서 트랜잭션 관리를 자동화하거나, ORM 프레임워크가 내부적으로 세이브포인트를 생성·해제하는 과정에서 예기치 않게 발생하는 경우가 많습니다.


주요 발생 원인

1. 존재하지 않는 세이브포인트 참조 (가장 빈번한 원인)

세이브포인트를 RELEASE 또는 ROLLBACK TO 명령으로 해제하거나 롤백한 이후에도, 이미 사라진 세이브포인트 이름을 다시 참조하려 할 때 이 에러가 발생합니다. 특히 루프 처리나 반복적인 배치 작업에서 동일한 세이브포인트 이름을 재사용하지 않고 반복 참조할 경우 자주 목격됩니다. 또한 중첩 트랜잭션 구조에서 내부 블록이 종료되면서 세이브포인트가 자동으로 해제된 것을 인지하지 못하는 경우도 원인이 됩니다.

2. 트랜잭션 블록 외부에서의 세이브포인트 사용

SAVEPOINT 명령은 반드시 활성화된 트랜잭션 블록(BEGIN ~ COMMIT/ROLLBACK) 내부에서만 사용 가능합니다. 트랜잭션을 명시적으로 시작하지 않은 자동 커밋(autocommit) 모드에서 세이브포인트를 생성하거나, 이미 커밋/롤백이 완료된 트랜잭션에서 세이브포인트를 조작하려 하면 에러가 발생합니다. 특히 psql 클라이언트나 JDBC, psycopg2 등 드라이버의 autocommit 설정에 따라 이 상황이 의도치 않게 발생할 수 있습니다.

3. ORM 및 커넥션 풀의 암묵적 세이브포인트 관리 충돌

Django, SQLAlchemy, Hibernate 등 ORM 프레임워크는 내부적으로 자동으로 세이브포인트를 생성하고 관리합니다. 애플리케이션 코드에서 수동으로 트랜잭션을 제어하거나, 커넥션 풀이 세션을 재활용하는 과정에서 세이브포인트 상태가 불일치하는 상황이 발생할 수 있습니다. 이 경우 ORM이 기대하는 세이브포인트 구조와 실제 데이터베이스의 세이브포인트 상태가 달라져 3B000 에러가 연쇄적으로 발생하게 됩니다.


해결 방법

원인 1: 존재하지 않는 세이브포인트 참조 해결

세이브포인트를 참조하기 전에 항상 세이브포인트의 생존 여부를 확인하는 방어적 코딩 패턴을 사용합니다.

-- 문제가 발생하는 코드 예시
BEGIN;

SAVEPOINT my_savepoint;

INSERT INTO orders (customer_id, amount) VALUES (101, 5000);

RELEASE SAVEPOINT my_savepoint;  -- 세이브포인트 해제

-- 이미 해제된 세이브포인트에 롤백 시도 → 3B000 에러 발생
ROLLBACK TO SAVEPOINT my_savepoint;

COMMIT;
-- 올바른 해결 방법: 세이브포인트를 해제하기 전에 롤백 필요 여부를 확인
BEGIN;

SAVEPOINT my_savepoint;

DO $$
DECLARE
    v_success BOOLEAN := TRUE;
BEGIN
    INSERT INTO orders (customer_id, amount) VALUES (101, 5000);

    -- 처리 결과에 따라 롤백 또는 해제 결정
    IF v_success THEN
        RELEASE SAVEPOINT my_savepoint;  -- 성공 시 해제
    ELSE
        ROLLBACK TO SAVEPOINT my_savepoint;  -- 실패 시 롤백
        RELEASE SAVEPOINT my_savepoint;      -- 롤백 후 반드시 해제
    END IF;
END;
$$;

COMMIT;
-- 배치 작업에서 세이브포인트를 반복 사용하는 올바른 패턴
BEGIN;

DO $$
DECLARE
    rec RECORD;
    sp_name TEXT;
BEGIN
    FOR rec IN SELECT id, amount FROM pending_orders LOOP
        sp_name := 'sp_order_' || rec.id;  -- 고유한 세이브포인트 이름 사용

        EXECUTE format('SAVEPOINT %I', sp_name);

        BEGIN
            UPDATE orders SET status = 'processed' WHERE id = rec.id;
            EXECUTE format('RELEASE SAVEPOINT %I', sp_name);
        EXCEPTION WHEN OTHERS THEN
            EXECUTE format('ROLLBACK TO SAVEPOINT %I', sp_name);
            EXECUTE format('RELEASE SAVEPOINT %I', sp_name);
            RAISE NOTICE '주문 % 처리 실패: %', rec.id, SQLERRM;
        END;
    END LOOP;
END;
$$;

COMMIT;

원인 2: 트랜잭션 블록 외부 세이브포인트 사용 해결

-- 잘못된 예시: autocommit 모드에서 세이브포인트 사용 (에러 발생)
-- (psql에서 \set AUTOCOMMIT on 상태)
SAVEPOINT sp1;  -- ERROR: 3B000 - 트랜잭션 블록 외부에서 세이브포인트 불가

-- 올바른 예시: 반드시 BEGIN으로 트랜잭션 시작 후 사용
BEGIN;

SAVEPOINT sp_insert_users;

INSERT INTO users (username, email) VALUES ('alice', 'alice@example.com');
INSERT INTO users (username, email) VALUES ('bob', 'bob@example.com');

-- 일부 실패 시나리오 처리
ROLLBACK TO SAVEPOINT sp_insert_users;
RELEASE SAVEPOINT sp_insert_users;

-- 다시 정상 데이터만 삽입
SAVEPOINT sp_insert_users_retry;
INSERT INTO users (username, email) VALUES ('alice', 'alice@example.com');
RELEASE SAVEPOINT sp_insert_users_retry;

COMMIT;
-- PostgreSQL에서 현재 트랜잭션 상태 확인 방법
SELECT
    txid_current() AS current_txid,
    pg_current_xact_id_if_assigned() AS assigned_xid,
    current_setting('transaction_isolation') AS isolation_level;

원인 3: ORM의 세이브포인트 충돌 해결 (Python/psycopg2 예시)

-- psycopg2에서 올바른 세이브포인트 관리 패턴 (Python 연동 시 참고용 SQL)

BEGIN;

-- ORM이 자동 생성하는 세이브포인트 형태 확인
-- Django의 경우 내부적으로 아래와 같이 생성함
SAVEPOINT "s140234567890_x1";

INSERT INTO app_product (name, price) VALUES ('Widget', 29900);

-- 정상 처리 후 해제
RELEASE SAVEPOINT "s140234567890_x1";

COMMIT;
-- 커넥션 풀 환경에서 세이브포인트 상태 초기화 방법
-- 커넥션 재사용 전 반드시 트랜잭션 정리
ROLLBACK;  -- 미완료 트랜잭션이 있으면 롤백

-- 또는 PgBouncer 사용 시, transaction 모드에서는
-- 세이브포인트가 세션 간 공유되지 않으므로 session 모드 사용 권장
-- postgresql.conf 또는 pgbouncer.ini 설정:
-- pool_mode = session

예방 방법

1. 세이브포인트 생명주기를 명확하게 관리하는 래퍼 함수 사용

프로덕션 환경에서는 세이브포인트의 생성, 사용, 해제 과정을 추상화한 PL/pgSQL 헬퍼 함수를 만들어 사용하는 것이 좋습니다. 이를 통해 세이브포인트의 이중 해제(double release)나 미해제(unreleased) 문제를 사전에 방지할 수 있습니다.

-- 세이브포인트 안전 처리를 위한 프로시저 예시
CREATE OR REPLACE PROCEDURE safe_execute_with_savepoint(
    p_sp_name TEXT,
    p_sql TEXT
)
LANGUAGE plpgsql AS $$
BEGIN
    EXECUTE format('SAVEPOINT %I', p_sp_name);

    BEGIN
        EXECUTE p_sql;
        EXECUTE format('RELEASE SAVEPOINT %I', p_sp_name);
    EXCEPTION WHEN OTHERS THEN
        EXECUTE format('ROLLBACK TO SAVEPOINT %I', p_sp_name);
        EXECUTE format('RELEASE SAVEPOINT %I', p_sp_name);
        RAISE;
    END;
END;
$$;

-- 사용 예시
BEGIN;
CALL safe_execute_with_savepoint('sp1', 'INSERT INTO audit_log (event) VALUES (''test'')');
COMMIT;

2. 애플리케이션 로그에서 세이브포인트 관련 에러를 별도 모니터링

postgresql.conf에서 log_min_error_statement = errorlog_line_prefix를 적절히 설정하여, 세이브포인트 관련 에러(SQLSTATE 3B000, 3B001)가 발생하면 즉시 알림을 받을 수 있도록 모니터링 체계를 구축해야 합니다. Datadog, PgBadger, pganalyze 등의 도구를 활용하면 세이브포인트 에러 패턴을 시각적으로 추적하고, 이상 징후를 조기에 발견할 수 있습니다.

-- 세이브포인트 관련 에러를 pg_stat_activity로 모니터링
SELECT
    pid,
    usename,
    application_name,
    state,
    query,
    state_change,
    wait_event_type,
    wait_event
FROM pg_stat_activity
WHERE state = 'idle in transaction'
  AND state_change < NOW() - INTERVAL '5 minutes'
ORDER BY state_change ASC;

관련 에러

  • 3B001invalid_savepoint_specification: 3B000의 하위 에러 코드로, 세이브포인트 이름이 유효하지 않거나 존재하지 않는 특정 세이브포인트를 지정했을 때 발생합니다. 3B000과 함께 가장 자주 마주치는 세이브포인트 관련 에러입니다.
  • 25000invalid_transaction_state: 트랜잭션 자체가 유효하지 않은 상태에서 세이브포인트를 조작할 때 함께 발생할 수 있는 에러입니다. 트랜잭션 블록 관리 문제와 연계됩니다.
  • 40001serialization_failure: 직렬화 격리 수준(SERIALIZABLE)에서 동시 트랜잭션 충돌로 인해 트랜잭션이 강제 롤백될 때 발생하며, 세이브포인트 재시도 로직 구현 시 함께 처리해야 합니다.
  • 25P02in_failed_sql_transaction: 트랜잭션 내에서 에러가 발생한 이후 적절한 ROLLBACK TO SAVEPOINTROLLBACK 없이 추가 SQL을 실행하려 할 때 발생합니다. 세이브포인트 예외 처리 미흡으로 인해 연쇄적으로 발생하는 경우가 많습니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기