2026년 08월 26일 | DBMS Error 가이드
이 글에서 다루는 내용
ORA-04088 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
ORA-04088 error during execution of trigger 는?
ORA-04088 에러는 Oracle 데이터베이스에서 트리거(Trigger)가 실행되는 도중 내부적으로 오류가 발생했을 때 나타나는 에러입니다. 이 에러 자체는 트리거 실행 실패를 알리는 “래퍼(Wrapper)” 에러로, 실제 근본 원인은 함께 출력되는 하위 에러(예: ORA-01400, ORA-00001, ORA-06502 등)에 있습니다. INSERT, UPDATE, DELETE, MERGE 등의 DML 작업이나 DDL 이벤트가 발생할 때 트리거가 자동으로 호출되며, 트리거 내부 로직에서 예외가 발생하면 해당 DML 작업 전체가 롤백됩니다.
주요 발생 원인
1. 트리거 내부의 데이터 제약 조건 위반 (NOT NULL, UNIQUE, FK 등)
트리거가 실행되면서 다른 테이블에 INSERT 또는 UPDATE를 시도할 때, 해당 컬럼이 NOT NULL 제약이나 UNIQUE 제약을 위반하는 경우 ORA-04088이 발생합니다. 예를 들어, 감사(Audit) 로그를 기록하는 트리거에서 필수 컬럼에 NULL 값이 삽입되면 ORA-01400 에러가 트리거되어 상위 ORA-04088으로 이어집니다. 실무에서 가장 빈번하게 발생하는 유형으로, 트리거 내부 DML의 데이터 품질을 철저히 검증해야 합니다.
2. 트리거 내부의 PL/SQL 런타임 오류 (변수 타입 불일치, 나누기 0, 버퍼 오버플로우 등)
트리거 내부에서 변수의 데이터 타입이 맞지 않거나, 숫자 연산에서 0으로 나누기가 발생하거나, VARCHAR2 변수에 허용 길이를 초과한 문자열을 대입하려 할 때 ORA-06502(VALUE_ERROR) 등의 PL/SQL 예외가 발생합니다. 이 예외가 트리거 내부에서 처리(EXCEPTION 블록)되지 않으면 그대로 상위 호출부로 전파되어 ORA-04088이 나타납니다. 특히 :NEW, :OLD 슈도 레코드(Pseudo Record)의 값을 잘못된 타입의 변수에 할당할 때 자주 발생합니다.
3. 변이 테이블(Mutating Table) 오류 – ORA-04091
DML이 실행 중인 테이블을 같은 트리거 내에서 SELECT 또는 DML로 다시 접근하려 할 때 ORA-04091(table is mutating) 에러가 발생하고, 이것이 ORA-04088의 원인이 됩니다. 예를 들어, EMPLOYEES 테이블에 UPDATE 트리거가 있고 해당 트리거 안에서 다시 EMPLOYEES 테이블을 조회하려 하면 변이 테이블 오류가 발생합니다. 이 문제는 COMPOUND TRIGGER나 패키지 전역 변수를 활용하여 우회할 수 있습니다.
해결 방법
원인 1 해결: 데이터 제약 조건 위반 처리
트리거 내부에서 DML을 실행하기 전에 NULL 여부 및 유효성을 검사하거나, EXCEPTION 블록으로 에러를 적절히 처리합니다.
-- 문제가 되는 트리거 예시
CREATE OR REPLACE TRIGGER trg_emp_audit
AFTER INSERT ON employees
FOR EACH ROW
BEGIN
-- audit_log 테이블의 user_name이 NOT NULL인데 NULL 삽입 시도
INSERT INTO audit_log (emp_id, user_name, action_date)
VALUES (:NEW.employee_id, NULL, SYSDATE); -- ORA-01400 발생
END;
/
-- 수정된 트리거: NULL 체크 및 기본값 처리
CREATE OR REPLACE TRIGGER trg_emp_audit
AFTER INSERT ON employees
FOR EACH ROW
DECLARE
v_user_name VARCHAR2(100);
BEGIN
-- NULL 방지: SYS_CONTEXT 또는 기본값 사용
v_user_name := NVL(SYS_CONTEXT('USERENV', 'SESSION_USER'), 'UNKNOWN');
INSERT INTO audit_log (emp_id, user_name, action_date)
VALUES (:NEW.employee_id, v_user_name, SYSDATE);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
-- 중복 키 예외 처리
UPDATE audit_log
SET action_date = SYSDATE
WHERE emp_id = :NEW.employee_id;
WHEN OTHERS THEN
-- 에러 로그 기록 후 재발생 (필요 시)
DBMS_OUTPUT.PUT_LINE('Audit trigger error: ' || SQLERRM);
RAISE; -- 상위로 에러 전파
END;
/
원인 2 해결: PL/SQL 런타임 오류 처리
변수 선언 시 적절한 타입과 크기를 지정하고, %TYPE 앵커를 활용하며 EXCEPTION 블록을 반드시 포함합니다.
-- 문제가 되는 트리거 예시 (타입 불일치)
CREATE OR REPLACE TRIGGER trg_salary_check
BEFORE UPDATE ON employees
FOR EACH ROW
DECLARE
v_ratio NUMBER(3,2); -- 정밀도 부족으로 오버플로우 가능
BEGIN
-- salary가 0이면 나누기 0 오류 발생
v_ratio := :NEW.salary / :OLD.salary * 100;
IF v_ratio > 150 THEN
RAISE_APPLICATION_ERROR(-20001, '급여 인상률이 150%를 초과할 수 없습니다.');
END IF;
END;
/
-- 수정된 트리거: 0 나누기 방지 및 타입 안전성 확보
CREATE OR REPLACE TRIGGER trg_salary_check
BEFORE UPDATE ON employees
FOR EACH ROW
DECLARE
v_ratio NUMBER(10, 4); -- 충분한 정밀도
v_old_sal employees.salary%TYPE; -- 앵커 타입 사용
BEGIN
v_old_sal := NVL(:OLD.salary, 0);
-- 나누기 0 방지
IF v_old_sal = 0 THEN
RETURN; -- 이전 급여가 0이면 검사 생략
END IF;
v_ratio := (:NEW.salary / v_old_sal) * 100;
IF v_ratio > 150 THEN
RAISE_APPLICATION_ERROR(-20001,
'급여 인상률(' || ROUND(v_ratio, 2) || '%)이 150%를 초과할 수 없습니다.');
END IF;
EXCEPTION
WHEN ZERO_DIVIDE THEN
NULL; -- 나누기 0은 무시
WHEN VALUE_ERROR THEN
RAISE_APPLICATION_ERROR(-20002, '급여 값 처리 중 데이터 오류가 발생했습니다.');
WHEN OTHERS THEN
RAISE;
END;
/
원인 3 해결: 변이 테이블(Mutating Table) 문제 해결
COMPOUND TRIGGER를 사용하여 행 레벨(Row-Level)에서 데이터를 수집하고, 문장 레벨(Statement-Level)에서 처리합니다.
-- 문제가 되는 트리거 (ORA-04091 유발)
CREATE OR REPLACE TRIGGER trg_dept_salary
AFTER UPDATE OF salary ON employees
FOR EACH ROW
DECLARE
v_avg_sal NUMBER;
BEGIN
-- employees 테이블이 변이 중인데 다시 SELECT → ORA-04091 발생
SELECT AVG(salary) INTO v_avg_sal FROM employees
WHERE department_id = :NEW.department_id;
IF :NEW.salary > v_avg_sal * 2 THEN
RAISE_APPLICATION_ERROR(-20003, '급여가 부서 평균의 2배를 초과합니다.');
END IF;
END;
/
-- 해결: COMPOUND TRIGGER 사용
CREATE OR REPLACE TRIGGER trg_dept_salary_compound
FOR UPDATE OF salary ON employees
COMPOUND TRIGGER
-- 타입 및 컬렉션 선언
TYPE t_dept_tab IS TABLE OF employees.department_id%TYPE;
TYPE t_sal_tab IS TABLE OF employees.salary%TYPE;
TYPE t_empid_tab IS TABLE OF employees.employee_id%TYPE;
v_dept_ids t_dept_tab := t_dept_tab();
v_new_sals t_sal_tab := t_sal_tab();
v_emp_ids t_empid_tab := t_empid_tab();
-- 행 레벨: 변경된 데이터를 컬렉션에 저장
AFTER EACH ROW IS
BEGIN
v_dept_ids.EXTEND;
v_new_sals.EXTEND;
v_emp_ids.EXTEND;
v_dept_ids(v_dept_ids.LAST) := :NEW.department_id;
v_new_sals(v_new_sals.LAST) := :NEW.salary;
v_emp_ids(v_emp_ids.LAST) := :NEW.employee_id;
END AFTER EACH ROW;
-- 문장 레벨: DML 완료 후 검증 수행 (변이 테이블 문제 없음)
AFTER STATEMENT IS
v_avg_sal NUMBER;
BEGIN
FOR i IN 1 .. v_dept_ids.COUNT LOOP
SELECT AVG(salary) INTO v_avg_sal
FROM employees
WHERE department_id = v_dept_ids(i);
IF v_new_sals(i) > v_avg_sal * 2 THEN
RAISE_APPLICATION_ERROR(-20003,
'EMP_ID ' || v_emp_ids(i) || ': 급여가 부서 평균의 2배를 초과합니다.');
END IF;
END LOOP;
END AFTER STATEMENT;
END trg_dept_salary_compound;
/
에러 원인 확인 방법
ORA-04088 발생 시 실제 원인 에러를 확인하는 방법입니다.
-- 1. SQL*Plus 또는 SQL Developer에서 전체 에러 스택 확인
-- 트리거 실행 후 에러 발생 시 아래 명령어로 상세 에러 확인
SHOW ERRORS TRIGGER trg_emp_audit;
-- 2. DBA_ERRORS 뷰로 트리거 컴파일 에러 확인
SELECT name, type, sequence, line, position, text
FROM dba_errors
WHERE name = 'TRG_EMP_AUDIT'
AND type = 'TRIGGER'
ORDER BY sequence;
-- 3. 트리거 상태 확인 (INVALID 상태 트리거 탐지)
SELECT trigger_name, status, trigger_type, triggering_event
FROM user_triggers
WHERE status = 'DISABLED' OR status = 'INVALID'
ORDER BY trigger_name;
-- 4. 특정 테이블의 모든 트리거 목록 확인
SELECT trigger_name, trigger_type, triggering_event, status
FROM user_triggers
WHERE table_name = 'EMPLOYEES'
ORDER BY trigger_name;
예방 방법
1. 트리거 내부에 반드시 EXCEPTION 블록 구현 및 에러 로깅 테이블 활용
모든 트리거에는 WHEN OTHERS 절을 포함한 EXCEPTION 블록을 작성하고, 발생한 에러를 별도의 에러 로그 테이블에 기록하는 습관을 들여야 합니다. 이렇게 하면 운영 중 예기치 않은 에러가 발생해도 원인 파악이 용이하며, 필요한 경우에만 RAISE로 상위에 에러를 전파하여 트랜잭션을 제어할 수 있습니다.
-- 에러 로그 테이블 생성
CREATE TABLE trigger_error_log (
log_id NUMBER GENERATED ALWAYS AS IDENTITY,
trigger_name VARCHAR2(100),
error_code NUMBER,
error_msg VARCHAR2(4000),
log_date DATE DEFAULT SYSDATE,
session_user VARCHAR2(100) DEFAULT SYS_CONTEXT('USERENV','SESSION_USER')
);
-- EXCEPTION 블록이 포함된 트리거 템플릿
CREATE OR REPLACE TRIGGER trg_best_practice_template
AFTER INSERT OR UPDATE OR DELETE ON employees
FOR EACH ROW
DECLARE
v_action VARCHAR2(10);
BEGIN
IF INSERTING THEN v_action := 'INSERT';
ELSIF UPDATING THEN v_action := 'UPDATE';
ELSE v_action := 'DELETE';
END IF;
-- 비즈니스 로직
INSERT INTO emp_audit_log (emp_id, action, changed_by, change_date)
VALUES (
NVL(:NEW.employee_id, :OLD.employee_id),
v_action,
SYS_CONTEXT('USERENV', 'SESSION_USER'),
SYSDATE
);
EXCEPTION
WHEN OTHERS THEN
-- 에러 로그 기록 (AUTONOMOUS TRANSACTION으로 독립 커밋)
INSERT INTO trigger_error_log (trigger_name, error_code, error_msg)
VALUES ('TRG_BEST_PRACTICE_TEMPLATE', SQLCODE, SUBSTR(SQLERRM, 1, 4000));
COMMIT; -- 로그만 독립 커밋 (PRAGMA AUTONOMOUS_TRANSACTION 필요)
RAISE; -- 원래 트랜잭션은 롤백
END;
/
2. 트리거 개발 전 단위 테스트 및 코드 리뷰 의무화
트리거를 운영 환경에 배포하기 전에 반드시 개발/테스트 환경에서 경계값(NULL, 최대 길이, 0 나누기 등), 동시 DML, 대량 데이터 시나리오를 포함한 단위 테스트를 수행해야 합니다. 또한, 모든 트리거 코드는 동료 DBA 또는 개발자의 코드 리뷰를 거치도록 프로세스를 수립하여 로직 결함을 사전에 차단하는 것이 중요합니다.
관련 에러
- ORA-04091: 트리거 내에서 변이 테이블(Mutating Table)에 접근할 때 발생하는 에러로 ORA-04088의 가장 흔한 하위 원인 중 하나입니다.
- ORA-06512: PL/SQL 에러 스택의 라인 번호를 알려주는 에러로, ORA-04088과 함께 출력되어 트리거의 정확한 오류 위치를 추적하는 데 사용됩니다.
- ORA-04098: 트리거가 INVALID 상태일 때 실행을 시도하면 발생하는 에러입니다. 트리거가 의존하는 객체가 변경되어 재컴파일이 필요한 경우 나타납니다.
- ORA-01400 / ORA-00001: 각각 NOT NULL 위반과 UNIQUE 제약 위반으로, 트리거 내부 DML에서 발생하여 ORA-04088을 유발하는 대표적인 에러입니다.
- ORA-04085: 트리거 내에서 :OLD 레코드 값을 변경하려 할 때 발생하는 에러로, BEFORE 트리거 로직 오류와 관련이 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.