Oracle ORA-20001 오류 원인과 해결 방법 완벽 가이드

ORA-20001
2026년 09월 29일 | DBMS Error 가이드

이 글에서 다루는 내용

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

ORA-20001 user defined error -20001 는?

ORA-20001은 Oracle 데이터베이스에서 개발자 또는 DBA가 PL/SQL 코드 내에서 RAISE_APPLICATION_ERROR 프로시저를 사용하여 의도적으로 발생시키는 사용자 정의 에러입니다. Oracle은 -20000부터 -20999까지의 에러 번호 범위를 사용자 정의 에러 전용으로 예약해 두었으며, 그 중 -20001은 가장 흔하게 사용되는 번호입니다. 이 에러는 비즈니스 로직 위반, 데이터 무결성 검사 실패, 또는 특정 조건에 부합하지 않는 상황에서 애플리케이션이 명시적으로 트랜잭션을 중단시키기 위해 발생시킵니다.


주요 발생 원인

1. RAISE_APPLICATION_ERROR를 이용한 비즈니스 규칙 위반 처리

가장 일반적인 원인으로, 개발자가 특정 비즈니스 규칙을 위반했을 때 애플리케이션 레이어로 명확한 오류 메시지를 전달하기 위해 RAISE_APPLICATION_ERROR(-20001, '메시지')를 명시적으로 호출합니다. 예를 들어, 재고 수량이 부족하거나 주문 금액이 허용 한도를 초과할 때 이 방식을 사용하여 트랜잭션을 롤백하고 에러를 상위 레이어로 전파합니다.

2. 트리거(Trigger) 내부에서의 데이터 무결성 검증 실패

데이터베이스 트리거 안에서 특정 컬럼 값의 유효성을 검사할 때 조건을 만족하지 못하면 ORA-20001이 발생합니다. 예를 들어 입력된 날짜가 과거 날짜이거나, 특정 상태 코드가 허용되지 않는 값일 경우 트리거가 자동으로 이 에러를 발생시켜 잘못된 데이터가 테이블에 저장되는 것을 방지합니다. 이 경우 에러 메시지를 보고 어떤 트리거가 활성화되었는지 추적하는 것이 중요합니다.

3. 저장 프로시저(Stored Procedure) 또는 패키지(Package) 내 예외 처리 로직

복잡한 저장 프로시저나 패키지 내부에서 예외 처리 블록(EXCEPTION WHEN OTHERS THEN)이 내부 에러를 잡아 ORA-20001로 변환하여 재발생시키는 경우가 있습니다. 이런 패턴은 내부 오류의 세부 정보를 숨기거나, 애플리케이션 표준 에러 코드로 통일하기 위해 사용되며, 원본 에러를 파악하기 위해 DBMS_UTILITY.FORMAT_ERROR_BACKTRACE 함수를 활용해야 합니다.


해결 방법

원인 1 해결: RAISE_APPLICATION_ERROR 호출 위치 파악 및 비즈니스 로직 수정

에러 메시지를 꼼꼼히 읽고 어떤 비즈니스 규칙이 위반되었는지 확인합니다. 아래 예제는 재고 부족 시 에러를 발생시키는 프로시저와, 이를 호출하는 측에서 예외를 처리하는 방법입니다.

-- 비즈니스 규칙 검사가 포함된 프로시저 예시
CREATE OR REPLACE PROCEDURE update_inventory(
    p_product_id IN NUMBER,
    p_quantity    IN NUMBER
) AS
    v_current_stock NUMBER;
BEGIN
    SELECT stock_qty INTO v_current_stock
    FROM   products
    WHERE  product_id = p_product_id
    FOR UPDATE;

    IF v_current_stock < p_quantity THEN
        RAISE_APPLICATION_ERROR(
            -20001,
            '재고 부족: 현재 재고(' || v_current_stock || ')가 요청 수량(' || p_quantity || ')보다 적습니다.'
        );
    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 update_inventory;
/

-- 호출 측에서 ORA-20001 처리 예시
BEGIN
    update_inventory(101, 500);
EXCEPTION
    WHEN OTHERS THEN
        IF SQLCODE = -20001 THEN
            DBMS_OUTPUT.PUT_LINE('사용자 정의 에러 발생: ' || SQLERRM);
            -- 필요한 경우 대체 로직 수행
        ELSE
            RAISE;
        END IF;
END;
/

원인 2 해결: 트리거 내부 로직 확인 및 검토

어떤 테이블에 대한 DML 작업 중 에러가 발생했는지 파악한 뒤, 해당 테이블에 연결된 트리거를 조회합니다.

-- 특정 테이블에 연결된 트리거 조회
SELECT trigger_name,
       trigger_type,
       triggering_event,
       status
FROM   user_triggers
WHERE  table_name = 'ORDERS'  -- 에러 발생 테이블명으로 변경
ORDER BY trigger_name;

-- 트리거 소스 코드 확인
SELECT text
FROM   user_source
WHERE  name = 'TRG_ORDERS_VALIDATE'  -- 해당 트리거 이름
AND    type = 'TRIGGER'
ORDER BY line;

-- 문제가 있는 트리거 수정 예시
CREATE OR REPLACE TRIGGER trg_orders_validate
BEFORE INSERT OR UPDATE ON orders
FOR EACH ROW
BEGIN
    -- 주문 날짜 유효성 검사
    IF :NEW.order_date < TRUNC(SYSDATE) THEN
        RAISE_APPLICATION_ERROR(
            -20001,
            '주문 날짜는 오늘(' || TO_CHAR(SYSDATE, 'YYYY-MM-DD') || ') 이후여야 합니다.'
        );
    END IF;

    -- 주문 금액 한도 검사
    IF :NEW.order_amount > 10000000 THEN
        RAISE_APPLICATION_ERROR(
            -20002,  -- 에러 구분을 위해 다른 번호 사용 권장
            '단일 주문 한도(10,000,000원)를 초과했습니다.'
        );
    END IF;
END trg_orders_validate;
/

원인 3 해결: 원본 에러 추적 및 백트레이스 활용

EXCEPTION 블록에서 에러가 래핑되어 ORA-20001로 변환된 경우, DBMS_UTILITY.FORMAT_ERROR_BACKTRACE를 활용해 원본 에러의 발생 위치를 추적합니다.

-- 에러 백트레이스를 활용한 디버깅 프로시저 예시
CREATE OR REPLACE PROCEDURE complex_process(p_id IN NUMBER) AS
    v_error_msg    VARCHAR2(4000);
    v_backtrace    VARCHAR2(4000);
BEGIN
    -- 복잡한 비즈니스 로직 수행
    process_step1(p_id);
    process_step2(p_id);
    process_step3(p_id);

EXCEPTION
    WHEN OTHERS THEN
        v_error_msg := SQLERRM;
        v_backtrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE;

        -- 에러 로그 테이블에 기록
        INSERT INTO error_log (
            log_date, error_code, error_message, backtrace, process_name
        ) VALUES (
            SYSDATE,
            SQLCODE,
            v_error_msg,
            v_backtrace,
            'complex_process'
        );
        COMMIT;

        -- 표준 에러 코드로 변환하여 재발생
        RAISE_APPLICATION_ERROR(
            -20001,
            '처리 중 오류 발생. 로그를 확인하세요. 원본 에러: ' || v_error_msg
        );
END complex_process;
/

-- 에러 로그 테이블 생성 (사전에 필요)
CREATE TABLE error_log (
    log_id       NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    log_date     DATE,
    error_code   NUMBER,
    error_message VARCHAR2(4000),
    backtrace    VARCHAR2(4000),
    process_name VARCHAR2(100)
);

예방 방법

1. 사용자 정의 에러 코드 표준화 및 문서화

팀 내에서 -20001부터 -20999 범위의 에러 코드를 체계적으로 관리하는 표준을 수립해야 합니다. 각 에러 코드에 대한 의미, 발생 조건, 처리 방법을 문서화하고, 에러 코드 관리 테이블을 데이터베이스에 유지하면 운영 중 혼란을 크게 줄일 수 있습니다. 무분별하게 -20001만 사용하는 것을 지양하고, 비즈니스 도메인별로 번호 범위를 분리하여 사용하는 것이 좋습니다.

-- 에러 코드 관리 테이블 예시
CREATE TABLE app_error_codes (
    error_code    NUMBER PRIMARY KEY,
    error_name    VARCHAR2(100) NOT NULL,
    description   VARCHAR2(500),
    domain        VARCHAR2(50),
    created_date  DATE DEFAULT SYSDATE
);

INSERT INTO app_error_codes VALUES (-20001, 'ERR_INVENTORY_SHORTAGE', '재고 부족 오류', 'INVENTORY', SYSDATE);
INSERT INTO app_error_codes VALUES (-20002, 'ERR_ORDER_LIMIT_EXCEEDED', '주문 한도 초과 오류', 'ORDER', SYSDATE);
INSERT INTO app_error_codes VALUES (-20010, 'ERR_INVALID_DATE', '유효하지 않은 날짜 오류', 'COMMON', SYSDATE);
COMMIT;

2. 공통 에러 처리 패키지 구현

애플리케이션 전반에서 일관된 방식으로 에러를 처리하고 로깅하기 위한 공통 패키지를 구현하면 유지보수성이 크게 향상됩니다. 에러 발생 시 자동으로 로그를 남기고 표준화된 메시지를 반환하는 패키지를 만들어 모든 프로시저와 트리거에서 재사용하면, 에러 추적과 디버깅이 훨씬 쉬워집니다.

-- 공통 에러 처리 패키지 예시
CREATE OR REPLACE PACKAGE pkg_error_handler AS
    PROCEDURE raise_error(
        p_error_code IN NUMBER,
        p_message    IN VARCHAR2 DEFAULT NULL
    );

    PROCEDURE log_error(
        p_error_code   IN NUMBER,
        p_error_msg    IN VARCHAR2,
        p_process_name IN VARCHAR2
    );
END pkg_error_handler;
/

CREATE OR REPLACE PACKAGE BODY pkg_error_handler AS
    PROCEDURE raise_error(
        p_error_code IN NUMBER,
        p_message    IN VARCHAR2 DEFAULT NULL
    ) AS
        v_base_msg VARCHAR2(500);
    BEGIN
        SELECT NVL(p_message, description)
        INTO   v_base_msg
        FROM   app_error_codes
        WHERE  error_code = p_error_code;

        log_error(p_error_code, v_base_msg, 'SYSTEM');
        RAISE_APPLICATION_ERROR(p_error_code, v_base_msg);
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            RAISE_APPLICATION_ERROR(-20001, NVL(p_message, '알 수 없는 오류가 발생했습니다.'));
    END raise_error;

    PROCEDURE log_error(
        p_error_code   IN NUMBER,
        p_error_msg    IN VARCHAR2,
        p_process_name IN VARCHAR2
    ) AS
        PRAGMA AUTONOMOUS_TRANSACTION;
    BEGIN
        INSERT INTO error_log (log_date, error_code, error_message, process_name)
        VALUES (SYSDATE, p_error_code, p_error_msg, p_process_name);
        COMMIT;
    END log_error;
END pkg_error_handler;
/

관련 에러

  • ORA-20000 ~ ORA-20999: Oracle이 사용자 정의 에러 전용으로 예약한 번호 범위로, ORA-20001과 동일한 메커니즘으로 동작합니다. 실무에서는 -20001을 범용적으로 사용하는 경우가 많지만, 에러 코드를 세분화하여 사용하는 것을 강력히 권장합니다.
  • ORA-06512: 스택 트레이스 정보를 포함하는 에러로, ORA-20001 발생 시 함께 출력되어 에러가 발생한 PL/SQL 라인 번호를 알려줍니다. 디버깅 시 반드시 함께 확인해야 합니다.
  • ORA-04088: 트리거 실행 중 에러가 발생했을 때 나타나는 에러로, 트리거 내부에서 ORA-20001이 발생한 경우 함께 출력될 수 있습니다.
  • ORA-01403 (NO_DATA_FOUND): 저장 프로시저 내에서 SELECT INTO 구문이 결과를 반환하지 못할 때 발생하며, 이를 EXCEPTION 블록에서 ORA-20001로 변환하는 패턴이 자주 사용됩니다.
DBMS 에러 코드 시리즈

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

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

댓글 남기기