2026년 08월 25일 | DBMS Error 가이드
이 글에서 다루는 내용
ORA-04084 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
ORA-04084 cannot change NEW values for this trigger type 는?
ORA-04084 에러는 트리거 내부에서 :NEW 값을 변경하려고 할 때, 해당 트리거 유형이 :NEW 값의 수정을 허용하지 않는 경우 발생하는 에러입니다. Oracle 트리거는 유형에 따라 :NEW와 :OLD 의사 레코드(pseudo record)에 대한 읽기/쓰기 권한이 엄격하게 제한되어 있습니다. 예를 들어 AFTER 트리거나 DELETE 트리거에서 :NEW 값을 수정하려 할 경우 이 에러가 발생하며, 이는 Oracle의 트리거 메커니즘에 대한 이해 부족에서 비롯되는 경우가 대부분입니다.
주요 발생 원인
- AFTER 트리거에서 :NEW 값 변경 시도
가장 흔한 발생 원인입니다. AFTER INSERT, AFTER UPDATE 트리거는 DML이 이미 완료된 시점 이후에 실행되기 때문에 :NEW 값을 변경하는 것은 의미가 없으며, Oracle은 이를 허용하지 않습니다. 데이터 무결성 관점에서 이미 커밋 전 데이터가 확정된 상태이므로, :NEW를 수정하려는 시도 자체가 논리적으로 불가합니다.
- DELETE 트리거에서 :NEW 값 변경 시도
DELETE 문은 행을 삭제하는 작업이므로 삽입되거나 갱신되는 새로운 값 자체가 존재하지 않습니다. 따라서 BEFORE DELETE 또는 AFTER DELETE 트리거에서 :NEW 값을 참조하거나 변경하려고 하면 ORA-04084가 발생합니다. :NEW는 오직 INSERT 또는 UPDATE 이벤트에서만 유효한 값을 가집니다.
- STATEMENT 레벨 트리거에서 :NEW 사용 시도
FOR EACH ROW 절 없이 작성된 문장 레벨(Statement-level) 트리거는 개별 행 단위로 동작하지 않기 때문에 :NEW와 :OLD 의사 레코드 자체를 사용할 수 없습니다. 이를 모르고 트리거 내에서 :NEW.column_name := value; 형태의 코드를 작성하면 ORA-04084 또는 유사한 컴파일/런타임 에러가 발생합니다.
해결 방법
원인 1 해결: AFTER 트리거를 BEFORE 트리거로 변경
:NEW 값을 변경해야 하는 비즈니스 로직은 반드시 BEFORE 트리거에서 처리해야 합니다. 아래와 같이 트리거 유형을 BEFORE INSERT OR UPDATE로 변경하면 문제가 해결됩니다.
-- 잘못된 예시: AFTER 트리거에서 :NEW 변경 시도 (ORA-04084 발생)
CREATE OR REPLACE TRIGGER trg_emp_after
AFTER INSERT ON employees
FOR EACH ROW
BEGIN
:NEW.salary := :NEW.salary * 1.1; -- ORA-04084 발생!
END;
/
-- 올바른 예시: BEFORE 트리거로 변경
CREATE OR REPLACE TRIGGER trg_emp_before
BEFORE INSERT OR UPDATE ON employees
FOR EACH ROW
BEGIN
-- 급여가 최소값 이하인 경우 자동 보정
IF :NEW.salary < 2000 THEN
:NEW.salary := 2000;
END IF;
-- 입력값 대문자 변환
:NEW.last_name := UPPER(:NEW.last_name);
END;
/
원인 2 해결: DELETE 트리거에서 :NEW 사용 제거
DELETE 트리거에서는 :OLD 값만 참조 가능하며, :NEW 값은 NULL입니다. 삭제 전 데이터를 로그 테이블에 저장하는 등의 용도로 :OLD를 사용하세요.
-- 삭제 로그 테이블 생성
CREATE TABLE emp_delete_log (
log_id NUMBER GENERATED ALWAYS AS IDENTITY,
employee_id NUMBER,
last_name VARCHAR2(50),
salary NUMBER,
deleted_at TIMESTAMP DEFAULT SYSTIMESTAMP
);
-- 올바른 DELETE 트리거: :OLD만 사용
CREATE OR REPLACE TRIGGER trg_emp_delete_log
BEFORE DELETE ON employees
FOR EACH ROW
BEGIN
-- :NEW 사용 불가, :OLD 값만 참조
INSERT INTO emp_delete_log (employee_id, last_name, salary)
VALUES (:OLD.employee_id, :OLD.last_name, :OLD.salary);
END;
/
-- 잘못된 예시 (절대 사용 금지)
-- :NEW.employee_id := 0; -- DELETE 트리거에서 ORA-04084 발생
원인 3 해결: FOR EACH ROW 절 추가 또는 :NEW 제거
Statement 레벨 트리거가 필요하다면 :NEW/:OLD 사용을 제거하고, 행 레벨 처리가 필요하다면 FOR EACH ROW를 반드시 추가해야 합니다.
-- 잘못된 예시: Statement 레벨 트리거에서 :NEW 사용
CREATE OR REPLACE TRIGGER trg_emp_stmt
BEFORE INSERT ON employees
-- FOR EACH ROW 없음!
BEGIN
:NEW.created_at := SYSDATE; -- 에러 발생
END;
/
-- 해결책 1: FOR EACH ROW 추가 (행 레벨 처리)
CREATE OR REPLACE TRIGGER trg_emp_row
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
:NEW.created_at := SYSDATE;
:NEW.created_by := SYS_CONTEXT('USERENV', 'SESSION_USER');
END;
/
-- 해결책 2: Statement 레벨 트리거에서 :NEW 제거 (감사 로그 등 다른 목적)
CREATE OR REPLACE TRIGGER trg_emp_stmt_audit
AFTER INSERT ON employees
BEGIN
-- :NEW 없이 테이블 전체 작업에 대한 처리
INSERT INTO audit_log (table_name, action, action_time)
VALUES ('EMPLOYEES', 'INSERT', SYSTIMESTAMP);
END;
/
트리거 유형별 :NEW / :OLD 참조 가이드
-- 트리거 유형별 :NEW, :OLD 가용성 확인 참고 쿼리
-- (실무에서 트리거 작성 전 반드시 이 규칙 확인)
/*
+-------------------+--------+--------+-----------+-----------+
| 트리거 유형 | :NEW 읽기| :NEW 쓰기| :OLD 읽기| :OLD 쓰기 |
+-------------------+--------+--------+-----------+-----------+
| BEFORE INSERT | 가능 | 가능 | 불가(NULL)| 불가 |
| AFTER INSERT | 가능 | 불가 | 불가(NULL)| 불가 |
| BEFORE UPDATE | 가능 | 가능 | 가능 | 불가 |
| AFTER UPDATE | 가능 | 불가 | 가능 | 불가 |
| BEFORE DELETE |불가(NULL)| 불가 | 가능 | 불가 |
| AFTER DELETE |불가(NULL)| 불가 | 가능 | 불가 |
+-------------------+--------+--------+-----------+-----------+
*/
-- 현재 DB에 존재하는 트리거 유형 확인
SELECT trigger_name,
trigger_type,
triggering_event,
table_name,
status
FROM user_triggers
WHERE table_name = 'EMPLOYEES'
ORDER BY trigger_name;
예방 방법
- 트리거 작성 전 유형 매트릭스 검토 습관화
트리거를 작성하기 전에 반드시 BEFORE/AFTER, INSERT/UPDATE/DELETE, ROW/STATEMENT 조합에 따른 :NEW와 :OLD 가용성을 확인하는 습관을 들여야 합니다. 팀 내 공통 개발 가이드라인 문서에 위의 가용성 매트릭스를 포함시키고, 코드 리뷰 시 체크리스트 항목으로 추가하는 것을 권장합니다. 특히 신규 개발자가 트리거를 처음 작성할 때 이 규칙을 놓치는 경우가 많으므로, 온보딩 교육에 반드시 포함시켜야 합니다.
- 트리거 컴파일 및 단위 테스트 자동화
CI/CD 파이프라인에 트리거 컴파일 검증 단계를 추가하여, 개발 환경에서 배포 전에 모든 트리거의 유효성을 자동으로 검사하도록 구성하는 것이 좋습니다. 아래와 같이 컴파일 오류 여부를 확인하는 쿼리를 자동화 스크립트에 포함시켜 빌드 실패 조건으로 활용하세요.
-- 컴파일 오류가 있는 트리거 확인 쿼리 (자동화 스크립트에 포함)
SELECT name,
type,
line,
position,
text AS error_message
FROM user_errors
WHERE type = 'TRIGGER'
ORDER BY name, line, position;
-- 특정 트리거 재컴파일
ALTER TRIGGER trg_emp_before COMPILE;
관련 에러
- ORA-04085:
:OLD값은 변경할 수 없습니다.:OLD의사 레코드는 읽기 전용이며, 모든 트리거 유형에서 쓰기가 불가합니다. - ORA-04082:
FOR EACH ROW절이 없는 Statement 레벨 트리거에서:NEW또는:OLD를 참조할 때 발생합니다. - ORA-04091: 트리거 내에서 자신이 속한 테이블을 변경(mutating table)하려 할 때 발생하는 에러로, 트리거 설계 오류와 함께 자주 등장합니다.
- ORA-04098: 트리거가 유효하지 않거나 재검증에 실패한 경우 발생하며, ORA-04084로 인해 트리거가 INVALID 상태가 된 후 DML 실행 시 나타날 수 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.