2026년 07월 03일 | DBMS Error 가이드
이 글에서 다루는 내용
ORA-01086 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
ORA-01086 savepoint never established 는?
ORA-01086 에러는 Oracle 데이터베이스에서 ROLLBACK TO SAVEPOINT 명령을 실행할 때, 해당 이름의 세이브포인트(Savepoint)가 현재 트랜잭션 내에 존재하지 않을 경우 발생하는 에러입니다. 세이브포인트는 트랜잭션 내의 특정 지점을 표시하여 부분적인 롤백을 가능하게 하는 기능인데, SAVEPOINT 명령으로 먼저 선언되지 않은 이름을 참조하면 이 에러가 발생합니다. 주로 애플리케이션 로직 오류, 트랜잭션 범위를 벗어난 세이브포인트 참조, 또는 오타로 인한 잘못된 세이브포인트 이름 사용이 원인이 됩니다.
주요 발생 원인
1. 세이브포인트를 선언하지 않고 ROLLBACK TO 명령 사용
가장 흔한 원인으로, 개발자가 SAVEPOINT 명령으로 세이브포인트를 먼저 설정하지 않은 채 ROLLBACK TO SAVEPOINT 명령을 실행하는 경우입니다. 예를 들어, 코드 리팩토링 과정에서 SAVEPOINT 선언부가 삭제되거나 주석 처리되었는데 ROLLBACK TO 구문은 그대로 남아 있을 때 자주 발생합니다. 이 경우 Oracle은 현재 트랜잭션 내에서 해당 이름의 세이브포인트를 찾을 수 없어 ORA-01086을 반환합니다.
2. COMMIT 또는 ROLLBACK 이후에 이전 세이브포인트를 참조하는 경우
COMMIT이나 ROLLBACK을 실행하면 현재 트랜잭션이 종료되고, 해당 트랜잭션에서 선언된 모든 세이브포인트도 함께 소멸됩니다. 이후 새로운 트랜잭션에서 이전에 선언된 세이브포인트 이름을 그대로 사용하려 하면 ORA-01086이 발생합니다. 특히 장기 실행 배치 프로그램이나 루프 처리 로직에서 트랜잭션 경계를 명확히 관리하지 않을 때 이런 문제가 자주 나타납니다.
3. 세이브포인트 이름 오타 또는 대소문자 불일치
Oracle에서 세이브포인트 이름은 대소문자를 구분하지 않지만, 애플리케이션 코드(Java, PL/SQL 등)에서 변수로 세이브포인트 이름을 동적으로 생성할 경우 이름이 일치하지 않을 수 있습니다. 예를 들어 SAVEPOINT my_save_point 로 선언했지만 ROLLBACK TO SAVEPOINT my_savepoint 처럼 오타가 있는 이름을 사용하면 동일한 에러가 발생합니다. 복잡한 PL/SQL 패키지나 여러 레이어로 구성된 애플리케이션에서 특히 주의해야 합니다.
해결 방법
원인 1 해결: ROLLBACK TO 전에 반드시 SAVEPOINT 선언
ROLLBACK TO 명령을 사용하기 전에 반드시 동일한 트랜잭션 내에서 SAVEPOINT를 먼저 선언해야 합니다.
-- 잘못된 예 (ORA-01086 발생)
BEGIN
UPDATE employees SET salary = salary * 1.1 WHERE department_id = 10;
-- SAVEPOINT 선언 없음
ROLLBACK TO SAVEPOINT before_update; -- ORA-01086 발생!
END;
/
-- 올바른 예
BEGIN
-- 먼저 세이브포인트를 선언
SAVEPOINT before_update;
UPDATE employees SET salary = salary * 1.1 WHERE department_id = 10;
-- 조건에 따라 부분 롤백
IF SQL%ROWCOUNT > 100 THEN
ROLLBACK TO SAVEPOINT before_update;
DBMS_OUTPUT.PUT_LINE('너무 많은 행이 영향받아 롤백 처리했습니다.');
ELSE
COMMIT;
DBMS_OUTPUT.PUT_LINE('업데이트 완료: ' || SQL%ROWCOUNT || '건');
END IF;
END;
/
원인 2 해결: 트랜잭션 경계 내에서 세이브포인트 관리
COMMIT 또는 ROLLBACK 이후에는 세이브포인트가 무효화되므로, 새 트랜잭션마다 세이브포인트를 재선언해야 합니다.
-- 루프 내 트랜잭션 처리 올바른 예
DECLARE
v_count NUMBER := 0;
BEGIN
FOR rec IN (SELECT employee_id, salary FROM employees WHERE department_id = 20) LOOP
-- 각 반복마다 새 세이브포인트 선언
SAVEPOINT sp_employee_update;
BEGIN
UPDATE employees
SET salary = rec.salary * 1.05
WHERE employee_id = rec.employee_id;
v_count := v_count + 1;
-- 10건마다 중간 커밋 (세이브포인트는 이 시점에 소멸됨)
IF MOD(v_count, 10) = 0 THEN
COMMIT;
DBMS_OUTPUT.PUT_LINE(v_count || '건 커밋 완료');
-- 이후 루프에서 SAVEPOINT가 다시 선언됨
END IF;
EXCEPTION
WHEN OTHERS THEN
-- 현재 트랜잭션 내 세이브포인트로 롤백
ROLLBACK TO SAVEPOINT sp_employee_update;
DBMS_OUTPUT.PUT_LINE('직원 ID ' || rec.employee_id || ' 처리 실패, 건너뜀');
END;
END LOOP;
COMMIT; -- 최종 커밋
DBMS_OUTPUT.PUT_LINE('전체 처리 완료: ' || v_count || '건');
END;
/
원인 3 해결: 동적 세이브포인트 이름 관리
동적으로 세이브포인트 이름을 생성할 경우 상수나 변수를 일관성 있게 사용합니다.
-- 동적 세이브포인트 이름 관리 예제
DECLARE
-- 세이브포인트 이름을 상수로 관리하여 오타 방지
c_sp_name CONSTANT VARCHAR2(30) := 'SP_BATCH_PROCESS';
v_sql VARCHAR2(200);
BEGIN
-- EXECUTE IMMEDIATE를 이용한 동적 세이브포인트 선언
v_sql := 'SAVEPOINT ' || c_sp_name;
EXECUTE IMMEDIATE v_sql;
-- 작업 수행
UPDATE orders SET status = 'PROCESSED' WHERE order_date < SYSDATE - 30;
-- 동일한 상수 이름으로 롤백 참조
v_sql := 'ROLLBACK TO SAVEPOINT ' || c_sp_name;
IF SQL%ROWCOUNT = 0 THEN
EXECUTE IMMEDIATE v_sql;
DBMS_OUTPUT.PUT_LINE('처리할 데이터 없음, 롤백 처리');
ELSE
COMMIT;
END IF;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('에러 발생: ' || SQLERRM);
END;
/
세이브포인트 존재 여부 사전 확인 (예방적 접근)
안타깝게도 Oracle에서는 세이브포인트 목록을 직접 조회하는 딕셔너리 뷰가 없습니다. 따라서 아래와 같이 예외 처리를 통해 방어적으로 코딩합니다.
-- 방어적 코딩: ORA-01086 예외 처리
DECLARE
e_savepoint_not_found EXCEPTION;
PRAGMA EXCEPTION_INIT(e_savepoint_not_found, -1086);
BEGIN
SAVEPOINT my_checkpoint;
-- 데이터 처리 로직
INSERT INTO audit_log (log_date, action) VALUES (SYSDATE, 'TEST');
-- 의도적으로 다른 이름으로 잘못 참조 (시뮬레이션)
ROLLBACK TO SAVEPOINT wrong_checkpoint;
EXCEPTION
WHEN e_savepoint_not_found THEN
DBMS_OUTPUT.PUT_LINE('세이브포인트를 찾을 수 없습니다. 전체 롤백을 수행합니다.');
ROLLBACK;
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END;
/
예방 방법
1. 세이브포인트 명명 규칙(Naming Convention) 수립 및 상수화
프로젝트 전체에서 세이브포인트 이름을 일관되게 관리하기 위해 PL/SQL 패키지 상수 또는 전용 네이밍 규칙을 도입하는 것이 Best Practice입니다. 예를 들어 SP_ 접두어를 붙이고 업무 모듈명을 포함하도록 규칙을 정하면 오타와 충돌을 방지할 수 있습니다. 또한 공통 패키지(common package)에 세이브포인트 이름을 상수로 선언하여 여러 프로그램에서 재사용하면 유지보수성이 크게 향상됩니다.
-- 공통 패키지에 세이브포인트 이름 상수 관리
CREATE OR REPLACE PACKAGE pkg_savepoint_names AS
c_sp_order_insert CONSTANT VARCHAR2(30) := 'SP_ORDER_INSERT';
c_sp_payment_update CONSTANT VARCHAR2(30) := 'SP_PAYMENT_UPDATE';
c_sp_stock_adjust CONSTANT VARCHAR2(30) := 'SP_STOCK_ADJUST';
END pkg_savepoint_names;
/
2. 트랜잭션 범위를 명확히 문서화하고 코드 리뷰 시 검증
모든 ROLLBACK TO SAVEPOINT 구문 위에 해당 세이브포인트가 선언된 위치를 주석으로 명시하는 습관을 들여야 합니다. 코드 리뷰 체크리스트에 “SAVEPOINT 선언 → DML 작업 → ROLLBACK TO 또는 COMMIT 순서 확인” 항목을 포함시키면 사전에 에러를 방지할 수 있습니다. 특히 PL/SQL 코드가 COMMIT을 포함한다면, 이후의 ROLLBACK TO 명령이 더 이상 유효하지 않다는 점을 반드시 팀 전체가 인지해야 합니다.
관련 에러
- ORA-01085: 이전 트랜잭션에서 DML이 보류 중인 상태에서 세이브포인트 관련 작업 시 발생할 수 있는 에러로, 트랜잭션 상태 관리와 관련이 있습니다.
- ORA-02074: 분산 트랜잭션(Distributed Transaction) 환경에서 SAVEPOINT를 사용할 수 없을 때 발생하며, ORA-01086과 함께 트랜잭션 제어 관련 에러로 묶여 나타나는 경우가 있습니다.
- ORA-01012: 로그인 상태가 아닐 때 트랜잭션 제어 명령을 실행하는 경우 발생하며, 세션이 끊어진 후 세이브포인트를 참조할 때 간접적으로 연관될 수 있습니다.
- ORA-00942: 세이브포인트 자체의 문제는 아니지만, 자동화 스크립트에서 트랜잭션 처리 중 테이블 접근 권한 에러와 함께 나타나는 경우 트랜잭션 롤백 로직에서 ORA-01086이 동반될 수 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.