2026년 09월 05일 | DBMS Error 가이드
이 글에서 다루는 내용
3B001 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
3B001 invalid savepoint specification 는?
PostgreSQL 에러 코드 3B001 invalid savepoint specification은 트랜잭션 내에서 세이브포인트(Savepoint)를 사용할 때 잘못된 이름이나 존재하지 않는 세이브포인트를 참조할 경우 발생합니다. 세이브포인트는 트랜잭션의 특정 지점을 표시하여 해당 지점으로 롤백할 수 있게 해주는 기능인데, 이 에러는 주로 ROLLBACK TO SAVEPOINT 또는 RELEASE SAVEPOINT 명령어를 실행할 때 지정한 세이브포인트 이름이 현재 트랜잭션 내에 존재하지 않거나 이미 해제(release)된 경우에 발생합니다. 실무에서는 복잡한 트랜잭션 로직을 처리하는 애플리케이션 코드나 저장 프로시저(Stored Procedure) 내에서 자주 마주치는 에러입니다.
주요 발생 원인
1. 존재하지 않는 세이브포인트 이름 참조
가장 흔한 원인으로, SAVEPOINT 명령으로 생성하지 않은 이름을 ROLLBACK TO SAVEPOINT 또는 RELEASE SAVEPOINT에서 참조하는 경우입니다. 오타(typo)나 대소문자 불일치, 혹은 세이브포인트 생성 자체를 빠뜨린 로직 오류가 이 문제를 유발합니다. 특히 동적으로 세이브포인트 이름을 생성하는 코드에서 이름 생성 규칙이 일관되지 않을 때 자주 발생합니다.
2. 이미 해제(RELEASE)된 세이브포인트 재참조
RELEASE SAVEPOINT 명령을 실행하면 해당 세이브포인트와 그 이후에 생성된 모든 세이브포인트가 제거됩니다. 이 상태에서 해제된 세이브포인트 이름으로 ROLLBACK TO SAVEPOINT를 시도하면 3B001 에러가 발생합니다. 재사용 가능한 트랜잭션 로직을 작성할 때 세이브포인트의 생명 주기(lifecycle)를 명확히 이해하지 못하면 이 문제가 반복됩니다.
3. 트랜잭션 블록 외부에서의 세이브포인트 명령 실행 또는 트랜잭션 중단 후 참조
세이브포인트는 반드시 활성 트랜잭션 블록(BEGIN ~ COMMIT/ROLLBACK) 내에서만 유효합니다. 트랜잭션 블록이 에러로 인해 중단(aborted) 상태가 된 후, 해당 트랜잭션 내에 설정된 세이브포인트로 돌아가려 할 때도 이 에러가 발생할 수 있습니다. 또한 ROLLBACK TO SAVEPOINT로 특정 세이브포인트로 롤백하면 그 이후에 설정된 세이브포인트들은 모두 무효화되므로, 이후 해당 이름들을 참조하면 에러가 발생합니다.
해결 방법
원인 1 해결: 세이브포인트 이름 사전 확인 및 올바른 명명
세이브포인트를 참조하기 전에 반드시 동일한 이름으로 생성되었는지 확인하세요. 아래는 잘못된 예와 올바른 예입니다.
-- ❌ 잘못된 예: 세이브포인트를 생성하지 않고 참조
BEGIN;
INSERT INTO orders (customer_id, amount) VALUES (101, 5000);
ROLLBACK TO SAVEPOINT sp_order_insert; -- ERROR: 3B001 invalid savepoint specification
COMMIT;
-- ✅ 올바른 예: 세이브포인트를 먼저 생성 후 참조
BEGIN;
INSERT INTO orders (customer_id, amount) VALUES (101, 5000);
SAVEPOINT sp_order_insert; -- 세이브포인트 생성
INSERT INTO order_items (order_id, product_id, qty) VALUES (1, 201, 3);
-- 만약 두 번째 INSERT에서 문제가 생기면 첫 번째 INSERT 지점으로 복구
ROLLBACK TO SAVEPOINT sp_order_insert; -- ✅ 정상 동작
COMMIT;
동적 세이브포인트 이름을 사용하는 경우에는 이름 생성 로직을 함수로 통일하세요.
-- 동적 세이브포인트 이름 관리 예시 (PL/pgSQL)
DO $$
DECLARE
v_sp_name TEXT := 'sp_' || to_char(now(), 'YYYYMMDDHH24MISS');
BEGIN
SAVEPOINT identifier_from_var; -- PL/pgSQL에서는 변수명 직접 사용 불가
-- 아래와 같이 EXECUTE를 활용
EXECUTE 'SAVEPOINT ' || v_sp_name;
INSERT INTO audit_log (action, created_at) VALUES ('TEST', now());
EXECUTE 'ROLLBACK TO SAVEPOINT ' || v_sp_name;
RAISE NOTICE '세이브포인트 % 로 롤백 완료', v_sp_name;
END;
$$;
원인 2 해결: 세이브포인트 해제 순서와 생명 주기 관리
세이브포인트를 RELEASE한 후에는 절대 해당 이름을 다시 참조하지 마세요. 중첩 세이브포인트를 사용할 때는 반드시 생성 역순으로 해제하는 패턴을 따르세요.
-- ❌ 잘못된 예: RELEASE 후 재참조
BEGIN;
SAVEPOINT sp_level1;
INSERT INTO products (name, price) VALUES ('Widget', 9900);
SAVEPOINT sp_level2;
INSERT INTO inventory (product_id, stock) VALUES (1, 100);
RELEASE SAVEPOINT sp_level2; -- sp_level2 해제
-- sp_level2는 이미 해제되어 존재하지 않음
ROLLBACK TO SAVEPOINT sp_level2; -- ERROR: 3B001 invalid savepoint specification
COMMIT;
-- ✅ 올바른 예: 생명 주기를 정확히 관리
BEGIN;
SAVEPOINT sp_level1;
INSERT INTO products (name, price) VALUES ('Widget', 9900);
SAVEPOINT sp_level2;
INSERT INTO inventory (product_id, stock) VALUES (1, 100);
-- inventory 삽입이 실패했다고 가정하고 sp_level1으로 롤백
ROLLBACK TO SAVEPOINT sp_level1; -- sp_level1은 아직 유효
-- sp_level2는 sp_level1 이후에 생성되었으므로 이미 무효화됨 (에러 발생 방지)
-- 여기서 다시 sp_level2를 참조하면 에러 발생하므로 참조 금지
INSERT INTO products (name, price) VALUES ('Gadget', 14900); -- 재시도
COMMIT;
원인 3 해결: 트랜잭션 상태 확인 및 에러 핸들링
PL/pgSQL의 예외 처리(Exception Handling)를 활용하여 세이브포인트 기반의 안전한 트랜잭션 패턴을 구현하세요.
-- PL/pgSQL에서 세이브포인트를 활용한 안전한 에러 처리 패턴
CREATE OR REPLACE FUNCTION safe_order_process(
p_customer_id INT,
p_product_id INT,
p_qty INT
) RETURNS TEXT AS $$
DECLARE
v_order_id INT;
v_result TEXT;
BEGIN
-- 주문 헤더 삽입
INSERT INTO orders (customer_id, created_at)
VALUES (p_customer_id, now())
RETURNING id INTO v_order_id;
-- 세이브포인트 설정 (주문 헤더 삽입 이후 지점)
SAVEPOINT sp_after_order_header;
BEGIN
-- 재고 차감 시도
UPDATE inventory
SET stock = stock - p_qty
WHERE product_id = p_product_id
AND stock >= p_qty;
IF NOT FOUND THEN
RAISE EXCEPTION '재고 부족: product_id=%', p_product_id;
END IF;
-- 주문 상세 삽입
INSERT INTO order_items (order_id, product_id, qty)
VALUES (v_order_id, p_product_id, p_qty);
v_result := '주문 처리 성공: order_id=' || v_order_id;
EXCEPTION WHEN OTHERS THEN
-- 세이브포인트로 롤백하여 주문 헤더는 유지하고 상세만 취소
ROLLBACK TO SAVEPOINT sp_after_order_header;
-- 실패 로그 기록 (세이브포인트 이후 작업이므로 안전)
INSERT INTO order_failures (order_id, reason, failed_at)
VALUES (v_order_id, SQLERRM, now());
v_result := '주문 처리 실패 (로그 기록됨): ' || SQLERRM;
END;
-- 세이브포인트 해제 (더 이상 필요 없음)
RELEASE SAVEPOINT sp_after_order_header;
RETURN v_result;
END;
$$ LANGUAGE plpgsql;
-- 함수 호출 예시
BEGIN;
SELECT safe_order_process(101, 201, 5);
COMMIT;
예방 방법
1. 세이브포인트 이름 상수화 및 중앙 관리
애플리케이션 코드나 PL/pgSQL 함수에서 세이브포인트 이름을 하드코딩된 문자열 리터럴로 분산 관리하면 오타나 불일치 문제가 발생하기 쉽습니다. 세이브포인트 이름을 상수(Constant) 또는 설정 테이블로 중앙화하여 관리하고, 가능하면 세이브포인트 생성 → 작업 → 커밋/롤백의 흐름을 하나의 래퍼(wrapper) 함수나 트랜잭션 관리 모듈로 캡슐화하세요. 이렇게 하면 이름 불일치로 인한 3B001 에러를 구조적으로 예방할 수 있습니다.
-- 세이브포인트 이름을 상수로 관리하는 예시
DO $$
DECLARE
-- 상수처럼 사용할 세이브포인트 이름 변수
C_SP_INVENTORY CONSTANT TEXT := 'sp_inventory_update';
C_SP_ORDER CONSTANT TEXT := 'sp_order_insert';
BEGIN
EXECUTE 'SAVEPOINT ' || C_SP_INVENTORY;
-- ... 재고 관련 작업 ...
EXECUTE 'SAVEPOINT ' || C_SP_ORDER;
-- ... 주문 관련 작업 ...
-- 롤백 시 동일한 상수 변수 사용 (오타 방지)
EXECUTE 'ROLLBACK TO SAVEPOINT ' || C_SP_INVENTORY;
RAISE NOTICE '세이브포인트 관리 완료';
END;
$$;
2. 세이브포인트 사용 전 트랜잭션 상태 검증 및 로깅
복잡한 비즈니스 로직에서는 세이브포인트를 사용하기 전에 현재 트랜잭션 상태와 세이브포인트 스택을 추적하는 로깅 메커니즘을 도입하세요. PostgreSQL의 pg_current_xact_id() 함수나 애플리케이션 레벨의 트랜잭션 컨텍스트 객체를 활용하여 세이브포인트의 생성, 해제, 롤백 시점을 기록하면, 에러 발생 시 원인 분석이 훨씬 용이해집니다. 또한 JDBC, psycopg2 등 드라이버 레벨의 세이브포인트 API를 직접 사용하는 경우, 드라이버가 자동으로 세이브포인트 이름을 관리해주는 기능을 적극 활용하세요.
관련 에러
3B000 invalid_savepoint_specification(계열 에러):3B001의 상위 클래스 에러 코드로, 세이브포인트 관련 규격 오류를 통칭합니다.25P01 no_active_sql_transaction: 활성 트랜잭션 블록 외부에서SAVEPOINT명령을 실행할 때 발생하며, 세이브포인트 사용 전BEGIN이 누락된 경우에 마주칩니다.25P02 in_failed_sql_transaction: 트랜잭션이 에러로 인해 이미 중단(aborted) 상태일 때,ROLLBACK TO SAVEPOINT이외의 명령을 실행하려 할 때 발생합니다. 이 상태에서는 반드시ROLLBACK또는 유효한 세이브포인트로의 롤백만 가능합니다.40001 serialization_failure: 직렬화 격리 수준에서 충돌이 발생할 때 나타나며, 이 에러 후 세이브포인트 처리를 잘못하면3B001로 이어질 수 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.