2026년 09월 29일 | DBMS Error 가이드
이 글에서 다루는 내용
ORA-20003 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
ORA-20003 user defined error -20003 는?
ORA-20003은 Oracle 데이터베이스에서 사용자 정의(User-Defined) 에러로, 개발자나 DBA가 PL/SQL 코드 내에서 RAISE_APPLICATION_ERROR 프로시저를 통해 의도적으로 발생시키는 커스텀 예외 오류입니다. Oracle은 -20000부터 -20999까지의 에러 번호 범위를 사용자 정의 에러 용도로 예약해 두고 있으며, ORA-20003은 그 중 하나입니다. 이 에러는 시스템 자체의 버그가 아니라 비즈니스 로직 위반, 데이터 유효성 검사 실패, 또는 특정 조건이 충족되지 않았을 때 개발자가 명시적으로 호출하는 예외입니다.
주요 발생 원인
- 비즈니스 로직 위반 시 RAISE_APPLICATION_ERROR 명시적 호출
가장 흔한 원인으로, 개발자가 특정 비즈니스 규칙을 코드로 구현하면서 조건 위반 시 ORA-20003을 발생시키도록 설계한 경우입니다. 예를 들어 재고 수량이 0 이하일 때 주문 처리를 막거나, 특정 상태값이 아닐 경우 트랜잭션을 거부하는 로직이 이에 해당합니다. 이 경우 에러 메시지를 함께 확인하면 어떤 비즈니스 규칙을 위반했는지 정확히 알 수 있습니다.
- 트리거(Trigger)에서 데이터 유효성 검사 실패
DML(INSERT, UPDATE, DELETE) 작업 시 테이블에 정의된 트리거가 데이터의 유효성을 검사하다가 조건을 만족하지 못하면 자동으로 ORA-20003을 발생시키는 경우입니다. 트리거 내부에서 RAISE_APPLICATION_ERROR(-20003, '사유') 형태로 구현되어 있으며, 이 경우 DML 작업 자체가 롤백됩니다. 특히 복잡한 참조 무결성이나 기간 중복 검사 등의 커스텀 제약 조건 구현에 자주 사용됩니다.
- 저장 프로시저 또는 함수의 예외 처리 블록에서 호출
패키지, 프로시저, 함수 내부의 EXCEPTION 블록에서 특정 조건을 감지하고 상위 호출자에게 의미 있는 에러 코드를 전달하기 위해 ORA-20003을 재발생시키는 경우입니다. 원래의 시스템 에러를 숨기고 사용자 친화적인 메시지로 감싸는 방식으로 활용되며, 이 경우 원본 에러 스택을 함께 분석해야 근본 원인을 파악할 수 있습니다. 대규모 엔터프라이즈 애플리케이션에서 에러 코드 체계를 표준화할 때 자주 사용되는 패턴입니다.
해결 방법
원인 1: 비즈니스 로직 위반
우선 에러 메시지 전체를 확인하여 어떤 조건이 위반되었는지 파악합니다. SQLERRM 또는 애플리케이션 로그에서 메시지를 확인하고, 해당 비즈니스 규칙에 맞게 입력 데이터를 수정합니다.
-- ORA-20003 발생 예시 (재고 부족 시 주문 차단 로직)
CREATE OR REPLACE PROCEDURE process_order(
p_product_id IN NUMBER,
p_quantity IN NUMBER
) AS
v_stock NUMBER;
BEGIN
SELECT stock_qty INTO v_stock
FROM products
WHERE product_id = p_product_id;
IF v_stock < p_quantity THEN
RAISE_APPLICATION_ERROR(-20003,
'재고 부족: 요청 수량(' || p_quantity ||
')이 현재 재고(' || v_stock || ')를 초과합니다.');
END IF;
-- 정상 주문 처리
UPDATE products
SET stock_qty = stock_qty - p_quantity
WHERE product_id = p_product_id;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END;
/
-- 호출 시 에러 처리 예시
BEGIN
process_order(101, 9999);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('에러 코드: ' || SQLCODE);
DBMS_OUTPUT.PUT_LINE('에러 메시지: ' || SQLERRM);
END;
/
원인 2: 트리거에서 발생하는 경우
트리거가 어떤 조건에서 에러를 발생시키는지 먼저 확인하고, 데이터를 조건에 맞게 수정하거나 트리거 로직을 검토합니다.
-- 트리거 내 유효성 검사 예시
CREATE OR REPLACE TRIGGER trg_check_salary
BEFORE INSERT OR UPDATE ON employees
FOR EACH ROW
BEGIN
-- 급여가 음수이거나 최대 한도 초과 시 ORA-20003 발생
IF :NEW.salary < 0 THEN
RAISE_APPLICATION_ERROR(-20003,
'유효하지 않은 급여: 급여는 0 이상이어야 합니다. 입력값=' || :NEW.salary);
END IF;
IF :NEW.salary > 999999999 THEN
RAISE_APPLICATION_ERROR(-20003,
'급여 한도 초과: 최대 허용 급여를 초과했습니다. 입력값=' || :NEW.salary);
END IF;
END;
/
-- 트리거 확인 쿼리 (어떤 트리거가 활성화되어 있는지 확인)
SELECT trigger_name, trigger_type, triggering_event, status
FROM user_triggers
WHERE table_name = 'EMPLOYEES';
-- 특정 트리거의 소스 코드 확인
SELECT text
FROM user_source
WHERE name = 'TRG_CHECK_SALARY'
ORDER BY line;
원인 3: 저장 프로시저/패키지에서 재발생하는 경우
호출 스택을 추적하여 에러의 근본 원인을 파악합니다. DBMS_UTILITY.FORMAT_ERROR_BACKTRACE를 활용하면 에러 발생 위치를 정확히 알 수 있습니다.
-- 에러 발생 위치 추적을 위한 패키지 예시
CREATE OR REPLACE PACKAGE error_handler_pkg AS
PROCEDURE safe_execute(p_id IN NUMBER);
END error_handler_pkg;
/
CREATE OR REPLACE PACKAGE BODY error_handler_pkg AS
PROCEDURE safe_execute(p_id IN NUMBER) AS
v_value VARCHAR2(100);
BEGIN
SELECT some_column INTO v_value
FROM some_table
WHERE id = p_id;
-- 조건 검사
IF v_value IS NULL OR LENGTH(TRIM(v_value)) = 0 THEN
RAISE_APPLICATION_ERROR(-20003,
'ID=' || p_id || '에 해당하는 유효한 값이 없습니다.');
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- 원본 에러를 사용자 정의 에러로 변환
RAISE_APPLICATION_ERROR(-20003,
'ID=' || p_id || '에 해당하는 데이터가 존재하지 않습니다. ' ||
'원본오류: ' || SQLERRM);
WHEN OTHERS THEN
-- 백트레이스 정보와 함께 에러 로깅
INSERT INTO error_log (error_code, error_msg, backtrace, log_time)
VALUES (SQLCODE, SQLERRM,
DBMS_UTILITY.FORMAT_ERROR_BACKTRACE,
SYSDATE);
COMMIT;
RAISE;
END safe_execute;
END error_handler_pkg;
/
-- 에러 발생 위치 확인 쿼리
BEGIN
error_handler_pkg.safe_execute(999);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('에러: ' || SQLERRM);
DBMS_OUTPUT.PUT_LINE('발생위치: ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
END;
/
-- 사용자 정의 에러 코드 범위에서 발생하는 에러 예외 처리 패턴
DECLARE
user_defined_error EXCEPTION;
PRAGMA EXCEPTION_INIT(user_defined_error, -20003);
BEGIN
-- 프로시저 호출
process_order(101, 9999);
EXCEPTION
WHEN user_defined_error THEN
DBMS_OUTPUT.PUT_LINE('비즈니스 규칙 위반: ' || SQLERRM);
-- 적절한 후처리 로직
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('예상치 못한 에러: ' || SQLERRM);
RAISE;
END;
/
예방 방법
- 표준화된 에러 코드 관리 체계 구축
프로젝트 또는 조직 내에서 -20000 ~ -20999 범위의 에러 코드를 체계적으로 관리하는 에러 코드 레지스트리를 만들어야 합니다. 예를 들어 -20001 ~ -20099는 주문 관련, -20100 ~ -20199는 재고 관련, -20200 ~ -20299는 회원 관련 에러로 구분하면 유지보수와 디버깅이 훨씬 쉬워집니다. 또한 에러 메시지에는 반드시 에러가 발생한 모듈명, 조건, 실제 값을 포함시켜 운영 중 문제 발생 시 빠른 원인 파악이 가능하도록 해야 합니다.
- 중앙 집중식 에러 로깅 및 모니터링 체계 구현
모든 사용자 정의 에러가 발생할 때 자동으로 에러 로그 테이블에 기록되도록 공통 에러 처리 패키지를 구현하고, 이를 모든 PL/SQL 코드에서 일관되게 사용하도록 코딩 표준을 정립해야 합니다. 에러 로그에는 에러 코드, 메시지, 발생 시각, 사용자 정보, 백트레이스 정보를 포함시키고, Oracle Enterprise Manager나 별도 모니터링 툴을 통해 특정 에러 코드의 발생 빈도를 추적하여 반복적인 비즈니스 로직 위반 패턴을 사전에 감지하는 체계를 갖추는 것이 좋습니다.
관련 에러
- ORA-20000 ~ ORA-20999: 모두 사용자 정의 에러 범위로, ORA-20003과 동일한 메커니즘으로 발생합니다. 프로젝트마다 어떤 번호에 어떤 의미를 부여했는지 내부 문서를 반드시 확인해야 합니다.
- ORA-06512: PL/SQL 에러 발생 시 호출 스택 정보를 나타내는 에러로, ORA-20003과 함께 자주 표시됩니다. 이 에러의 라인 번호를 추적하면 에러 발생 위치를 정확히 찾을 수 있습니다.
- ORA-04088: 트리거 실행 중 에러가 발생했음을 나타내는 에러로, 트리거 내에서 ORA-20003이 발생한 경우 함께 나타납니다.
- ORA-01403 (NO_DATA_FOUND): 사용자 정의 에러로 변환되기 전 원본 에러로 자주 등장하며, 이를 ORA-20003으로 래핑하는 패턴이 실무에서 흔히 사용됩니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.