2026년 07월 01일 | DBMS Error 가이드
이 글에서 다루는 내용
3B000 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
3B000 savepoint exception 는?
PostgreSQL에서 3B000: savepoint exception은 트랜잭션 내에서 세이브포인트(savepoint)를 잘못 사용하거나, 존재하지 않는 세이브포인트를 참조하려 할 때 발생하는 에러입니다. 세이브포인트는 트랜잭션 중간에 부분적인 롤백 지점을 설정하는 기능으로, 복잡한 트랜잭션 제어에 매우 유용하게 사용됩니다. 그러나 잘못된 이름 참조, 트랜잭션 외부 사용, 이미 해제된 세이브포인트 접근 등 다양한 상황에서 이 에러가 트리거될 수 있습니다.
주요 발생 원인
1. 존재하지 않는 세이브포인트 이름 참조
가장 흔한 원인으로, ROLLBACK TO SAVEPOINT 또는 RELEASE SAVEPOINT 구문에서 이름을 오타 내거나, 이미 해제(RELEASE)된 세이브포인트를 다시 참조할 때 발생합니다. PostgreSQL은 세이브포인트 이름을 대소문자를 구분하지 않고 처리하지만, 해제된 이후에는 해당 이름이 더 이상 유효하지 않습니다. 따라서 세이브포인트의 라이프사이클을 정확하게 관리하지 않으면 반드시 이 에러가 발생합니다.
2. 트랜잭션 블록 외부에서 세이브포인트 사용
SAVEPOINT, ROLLBACK TO SAVEPOINT, RELEASE SAVEPOINT 명령은 반드시 BEGIN으로 시작된 명시적 트랜잭션 블록 내에서 실행되어야 합니다. 자동 커밋(auto-commit) 모드가 활성화된 상태에서 세이브포인트 관련 명령을 실행하면 트랜잭션 컨텍스트가 없으므로 에러가 발생합니다. 특히 psql 클라이언트나 애플리케이션 레이어에서 자동 커밋 설정을 제대로 확인하지 않은 경우 이 문제가 자주 발생합니다.
3. 이미 해제(RELEASE)된 세이브포인트 재사용
RELEASE SAVEPOINT 명령은 세이브포인트와 그 이후에 설정된 모든 세이브포인트를 제거합니다. 이 명령 실행 후 같은 이름 또는 하위에 위치했던 세이브포인트를 다시 롤백하거나 참조하려 할 때 3B000 에러가 발생합니다. 복잡한 중첩 세이브포인트 구조를 사용하는 환경에서 특히 자주 마주치는 문제이며, 세이브포인트의 계층 구조를 명확히 이해해야 해결할 수 있습니다.
해결 방법
원인 1 해결: 세이브포인트 이름 정확히 확인하기
존재하는 세이브포인트 목록은 PostgreSQL에서 직접 조회할 수 없지만, 트랜잭션 내에서 명확하게 이름을 정의하고 참조하는 습관이 중요합니다.
-- 잘못된 예: 존재하지 않는 세이브포인트 참조
BEGIN;
SAVEPOINT my_savepoint;
INSERT INTO orders (product_id, quantity) VALUES (101, 5);
ROLLBACK TO SAVEPOINT my_savepint; -- 오타! 에러 발생
COMMIT;
-- 올바른 예: 정확한 이름으로 참조
BEGIN;
SAVEPOINT my_savepoint;
INSERT INTO orders (product_id, quantity) VALUES (101, 5);
-- 에러 발생 시 올바른 세이브포인트로 롤백
ROLLBACK TO SAVEPOINT my_savepoint;
-- 이후 다시 정상 작업 진행
INSERT INTO orders (product_id, quantity) VALUES (102, 3);
COMMIT;
애플리케이션에서 세이브포인트 이름을 동적으로 생성하는 경우 상수 또는 열거형으로 관리하여 오타를 방지하세요.
원인 2 해결: 명시적 트랜잭션 블록 내에서 세이브포인트 사용
-- 잘못된 예: 트랜잭션 없이 세이브포인트 사용 (자동 커밋 모드)
SAVEPOINT sp1; -- ERROR: 3B000 - SAVEPOINT can only be used in transaction blocks
-- 올바른 예: BEGIN으로 트랜잭션 블록 시작 후 사용
BEGIN;
SAVEPOINT sp_before_update;
UPDATE accounts SET balance = balance - 1000 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE account_id = 2;
-- 두 번째 업데이트에 문제가 있으면 첫 번째 지점으로 롤백
ROLLBACK TO SAVEPOINT sp_before_update;
-- 수정 후 재시도
UPDATE accounts SET balance = balance - 500 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 500 WHERE account_id = 2;
COMMIT;
Python(psycopg2) 또는 Java(JDBC) 같은 드라이버를 사용할 경우 반드시 autocommit = False로 설정해야 합니다.
-- PL/pgSQL 함수 내에서 세이브포인트 활용 예제
CREATE OR REPLACE FUNCTION safe_transfer(
p_from_account INT,
p_to_account INT,
p_amount NUMERIC
) RETURNS BOOLEAN AS $$
BEGIN
-- 함수 내부는 이미 트랜잭션 컨텍스트 내에 있음
SAVEPOINT transfer_start;
UPDATE accounts SET balance = balance - p_amount
WHERE account_id = p_from_account;
IF NOT FOUND THEN
ROLLBACK TO SAVEPOINT transfer_start;
RETURN FALSE;
END IF;
UPDATE accounts SET balance = balance + p_amount
WHERE account_id = p_to_account;
IF NOT FOUND THEN
ROLLBACK TO SAVEPOINT transfer_start;
RETURN FALSE;
END IF;
RELEASE SAVEPOINT transfer_start;
RETURN TRUE;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK TO SAVEPOINT transfer_start;
RETURN FALSE;
END;
$$ LANGUAGE plpgsql;
원인 3 해결: RELEASE 후 세이브포인트 재참조 방지
BEGIN;
SAVEPOINT sp1;
INSERT INTO log_table (message) VALUES ('Step 1 completed');
SAVEPOINT sp2;
INSERT INTO log_table (message) VALUES ('Step 2 completed');
-- sp2 해제
RELEASE SAVEPOINT sp2;
-- 잘못된 예: 이미 해제된 sp2로 롤백 시도
-- ROLLBACK TO SAVEPOINT sp2; -- 에러 발생!
-- 올바른 예: sp1은 아직 유효하므로 이것으로 롤백 가능
ROLLBACK TO SAVEPOINT sp1;
-- 새로운 세이브포인트를 다시 설정
SAVEPOINT sp2_new;
INSERT INTO log_table (message) VALUES ('Step 2 retry completed');
COMMIT;
예방 방법
1. 세이브포인트 이름을 코드 레벨에서 상수로 관리
세이브포인트 이름을 하드코딩된 문자열로 직접 쓰는 대신, 애플리케이션 코드에서 상수 또는 설정 파일로 중앙 관리하세요. 이름 오타로 인한 3B000 에러의 절반 이상이 이 방법으로 예방됩니다. 또한 세이브포인트를 생성, 해제, 롤백하는 각 단계를 로깅하면 디버깅 시 빠른 원인 파악이 가능합니다.
-- 예: 저장된 세이브포인트 이름 패턴 사용
-- 애플리케이션에서 'sp_' + 타임스탬프 또는 UUID 조합으로 고유 이름 생성
BEGIN;
SAVEPOINT sp_20240101_001;
-- 작업 수행...
ROLLBACK TO SAVEPOINT sp_20240101_001;
COMMIT;
2. 예외 처리 블록에서 세이브포인트 상태를 명확히 추적
PL/pgSQL이나 애플리케이션 코드에서 세이브포인트를 사용할 때는 반드시 EXCEPTION 블록 또는 try-catch 구문을 통해 세이브포인트의 유효성을 보장하세요. 세이브포인트 생성 여부를 불리언 플래그나 스택 자료구조로 추적하면 RELEASE 또는 ROLLBACK TO 명령을 안전하게 호출할 수 있습니다.
-- PL/pgSQL에서 안전한 세이브포인트 패턴
DO $$
DECLARE
v_savepoint_set BOOLEAN := FALSE;
BEGIN
SAVEPOINT safe_point;
v_savepoint_set := TRUE;
-- 위험한 작업 수행
INSERT INTO critical_table (data) VALUES ('important data');
RELEASE SAVEPOINT safe_point;
v_savepoint_set := FALSE;
EXCEPTION
WHEN OTHERS THEN
IF v_savepoint_set THEN
ROLLBACK TO SAVEPOINT safe_point;
END IF;
RAISE NOTICE 'Error caught: %', SQLERRM;
END;
$$;
관련 에러
3B001: invalid_savepoint_specification:3B000의 하위 에러 코드로, 세이브포인트 이름이 유효하지 않거나 존재하지 않을 때 구체적으로 발생합니다. 대부분의 실제 세이브포인트 에러는 이 코드로 보고됩니다.25000: invalid_transaction_state: 트랜잭션 상태 자체가 잘못된 경우 발생하며, 세이브포인트 에러와 함께 나타날 수 있습니다.25P01: no_active_sql_transaction: 활성 트랜잭션 없이 세이브포인트 관련 명령을 실행할 때 발생하며, 원인 2와 밀접하게 연관됩니다.40000: transaction_rollback: 트랜잭션 전체가 롤백되어 세이브포인트 자체가 무효화될 때 함께 볼 수 있는 에러입니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.