PL/SQL

PL/SQL 기초 06강 — 예외 처리

🎯 학습 목표

  • EXCEPTION 섹션에서 사전정의 예외를 처리하는 패턴을 익힌다.
  • WHEN OTHERS THEN과 SQLCODE/SQLERRM으로 예상 못 한 오류를 처리한다.
  • RAISE로 예외를 다시 올리고, 사용자 정의 예외와 RAISE_APPLICATION_ERROR를 만든다.

📖 예외 처리의 흐름

PL/SQL 블록에서 오류가 발생하면 실행이 즉시 EXCEPTION 섹션으로 점프합니다. EXCEPTION 섹션에서 처리되지 않으면 오류가 호출자에게 전파(propagate)됩니다.

BEGIN
  (정상 실행)
  오류 발생 --------------------------->
                                       EXCEPTION
  WHEN 예외1 THEN ...  <-- 처리됨 --> 블록 정상 종료
  WHEN 예외2 THEN ...
  WHEN OTHERS THEN ...  <-- 나머지 모두
END;    <-- 처리 안 되면 오류가 위로 전파

📖 사전정의 예외 (Named Exceptions)

DECLARE
  v_sal emp.sal%TYPE;
BEGIN
  SELECT sal INTO v_sal FROM emp WHERE empno = 9999;  -- 없는 사원
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    DBMS_OUTPUT.PUT_LINE('[오류] 해당 사원 없음');
  WHEN TOO_MANY_ROWS THEN
    DBMS_OUTPUT.PUT_LINE('[오류] 조회 결과가 두 건 이상');
  WHEN ZERO_DIVIDE THEN
    DBMS_OUTPUT.PUT_LINE('[오류] 0으로 나눌 수 없음');
  WHEN DUP_VAL_ON_INDEX THEN
    DBMS_OUTPUT.PUT_LINE('[오류] 중복 키 입력');
  WHEN VALUE_ERROR THEN
    DBMS_OUTPUT.PUT_LINE('[오류] 타입 또는 크기 오류');
END;
/

자주 쓰는 사전정의 예외: NO_DATA_FOUND, TOO_MANY_ROWS, ZERO_DIVIDE, DUP_VAL_ON_INDEX, VALUE_ERROR, CURSOR_ALREADY_OPEN, INVALID_NUMBER.

💻 WHEN OTHERS THEN — 포괄 처리

BEGIN
  INSERT INTO some_table VALUES (1, '테스트');
  COMMIT;
EXCEPTION
  WHEN DUP_VAL_ON_INDEX THEN
    DBMS_OUTPUT.PUT_LINE('중복: ' || SQLERRM);
    ROLLBACK;
  WHEN OTHERS THEN
    -- SQLCODE: Oracle 오류 번호 (음수)
    -- SQLERRM: 오류 메시지 문자열
    DBMS_OUTPUT.PUT_LINE('예상치 못한 오류 [' || SQLCODE || ']: ' || SQLERRM);
    ROLLBACK;
    RAISE;   -- 처리 후 오류를 다시 위로 전파
END;
/

WHEN OTHERS THEN은 반드시 마지막에 위치해야 합니다. 그 뒤에 다른 WHEN을 쓰면 컴파일 오류입니다.

💻 사용자 정의 예외와 RAISE

DECLARE
  e_low_stock EXCEPTION;     -- 사용자 정의 예외 선언
  v_qty NUMBER := 3;
BEGIN
  IF v_qty < 5 THEN
    RAISE e_low_stock;       -- 명시적으로 예외 발생
  END IF;
  DBMS_OUTPUT.PUT_LINE('재고 충분: ' || v_qty);
EXCEPTION
  WHEN e_low_stock THEN
    DBMS_OUTPUT.PUT_LINE('[경고] 재고 부족: ' || v_qty || '개');
END;
/

💻 RAISE_APPLICATION_ERROR — 의미 있는 오류 번호와 메시지

CREATE OR REPLACE PROCEDURE check_age(p_age IN NUMBER) AS
BEGIN
  IF p_age < 0 THEN
    -- 오류 번호는 -20000 ~ -20999 범위에서 자유롭게 지정
    RAISE_APPLICATION_ERROR(-20001, '나이는 음수일 수 없습니다: ' || p_age);
  END IF;
  DBMS_OUTPUT.PUT_LINE('나이: ' || p_age);
END;
/

호출자(애플리케이션)에 특정 오류 번호와 메시지를 전달할 때 씁니다. 사용자 정의 예외는 블록 내부에서 처리하는 반면, RAISE_APPLICATION_ERROR는 외부 호출자에게도 의미 있는 오류를 전달합니다.

⚠️ 주의사항

  • EXCEPTION 섹션에서 오류가 발생하면 그 오류는 같은 블록 안에서 처리되지 않고 바깥으로 전파됩니다. 중첩 블록 안에서 각각 처리하거나 RAISE로 위임하세요.
  • WHEN OTHERS THEN NULL;처럼 오류를 그냥 삼키는 코드는 매우 위험합니다. 최소한 로그를 남기거나 RAISE로 전파하세요.
  • EXCEPTION 섹션에서는 GOTO로 BEGIN 구역으로 돌아갈 수 없습니다. 재시도 로직이 필요하면 중첩 블록이나 반복문 구조로 설계하세요.

💡 팁

  • 프로시저·함수에서 오류 로그 테이블에 INSERT한 뒤 RAISE하는 패턴이 실무에서 많이 쓰입니다. 오류 이력을 추적하면서도 호출자에게 오류를 알릴 수 있습니다.
  • DBMS_UTILITY.FORMAT_ERROR_BACKTRACE()를 WHEN OTHERS 안에서 쓰면 오류가 발생한 정확한 줄 번호를 알 수 있어 디버깅에 유용합니다.