2026년 08월 24일 | DBMS Error 가이드
이 글에서 다루는 내용
ORA-04076 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
ORA-04076 invalid NEW or OLD specification 는?
ORA-04076 에러는 Oracle 트리거(Trigger) 내에서 :NEW 또는 :OLD 의사 레코드(Pseudo-record)를 잘못된 방식으로 참조할 때 발생합니다. 이 에러는 주로 트리거의 종류(BEFORE/AFTER, INSERT/UPDATE/DELETE)와 맞지 않는 방식으로 :NEW 또는 :OLD 값을 사용하거나, 문(Statement) 레벨 트리거에서 행(Row) 레벨 전용 예약어를 사용할 때 나타납니다. 트리거 작성 시 Oracle이 허용하는 :NEW와 :OLD의 사용 규칙을 정확히 숙지하지 않으면 반드시 마주치게 되는 에러입니다.
주요 발생 원인
1. Statement 레벨 트리거에서 :NEW 또는 :OLD 사용
Oracle 트리거는 크게 행 레벨(ROW-LEVEL, FOR EACH ROW)과 문 레벨(STATEMENT-LEVEL) 두 가지로 나뉩니다. :NEW와 :OLD는 오직 행 레벨 트리거(FOR EACH ROW 절이 있는 트리거)에서만 사용할 수 있으며, 문 레벨 트리거에서 이를 참조하면 ORA-04076이 발생합니다. 많은 개발자들이 FOR EACH ROW를 빠뜨린 채 :NEW나 :OLD를 사용하려다 이 에러를 경험합니다.
2. DELETE 트리거에서 :NEW 참조, INSERT 트리거에서 :OLD 참조
Oracle의 트리거 동작 규칙상, DELETE 트리거에서는 삭제될 데이터를 나타내는 :OLD만 유효하고 :NEW는 NULL이거나 의미가 없습니다. 반대로 INSERT 트리거에서는 새로 삽입될 데이터인 :NEW만 유효하며 :OLD는 항상 NULL입니다. 잘못된 컨텍스트에서 이를 수정(assign)하려 할 경우 ORA-04076이 발생할 수 있습니다.
3. BEFORE INSERT 트리거에서 :OLD 값을 변경하려는 시도, 또는 AFTER 트리거에서 :NEW 값을 변경하려는 시도
:NEW 값은 BEFORE 트리거에서만 변경(수정)이 가능합니다. AFTER 트리거에서 :NEW 값에 값을 할당하려 하면 Oracle은 이를 허용하지 않고 에러를 발생시킵니다. 또한 :OLD 값은 읽기 전용(READ-ONLY)이므로 어떤 트리거에서도 :OLD 컬럼 값을 수정하려고 하면 에러가 발생합니다.
해결 방법
원인 1 해결: FOR EACH ROW 추가
Statement 레벨 트리거에서 :NEW/:OLD를 사용하려면 반드시 FOR EACH ROW 절을 추가해야 합니다.
잘못된 예시 (에러 발생):
-- FOR EACH ROW 없이 :NEW 참조 시 ORA-04076 발생
CREATE OR REPLACE TRIGGER trg_emp_salary_check
BEFORE UPDATE ON employees
-- FOR EACH ROW 절 누락!
BEGIN
IF :NEW.salary < 1000 THEN -- 여기서 ORA-04076 발생
RAISE_APPLICATION_ERROR(-20001, '급여는 1000 이상이어야 합니다.');
END IF;
END;
/
올바른 예시 (수정 후):
-- FOR EACH ROW를 추가하여 행 레벨 트리거로 변경
CREATE OR REPLACE TRIGGER trg_emp_salary_check
BEFORE UPDATE ON employees
FOR EACH ROW -- 반드시 추가
BEGIN
IF :NEW.salary < 1000 THEN
RAISE_APPLICATION_ERROR(-20001, '급여는 1000 이상이어야 합니다.');
END IF;
END;
/
원인 2 해결: 트리거 이벤트에 맞는 :NEW/:OLD 사용
DML 이벤트별로 유효한 의사 레코드를 올바르게 구분하여 사용해야 합니다.
-- INSERT: :NEW만 유효 (:OLD는 모두 NULL)
CREATE OR REPLACE TRIGGER trg_emp_insert_log
AFTER INSERT ON employees
FOR EACH ROW
BEGIN
INSERT INTO audit_log (action, emp_id, new_salary, log_date)
VALUES ('INSERT', :NEW.employee_id, :NEW.salary, SYSDATE);
-- :OLD.salary는 NULL이므로 사용 불필요
END;
/
-- UPDATE: :NEW와 :OLD 모두 유효
CREATE OR REPLACE TRIGGER trg_emp_update_log
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
INSERT INTO audit_log (action, emp_id, old_salary, new_salary, log_date)
VALUES ('UPDATE', :NEW.employee_id, :OLD.salary, :NEW.salary, SYSDATE);
END;
/
-- DELETE: :OLD만 유효 (:NEW는 모두 NULL)
CREATE OR REPLACE TRIGGER trg_emp_delete_log
AFTER DELETE ON employees
FOR EACH ROW
BEGIN
INSERT INTO audit_log (action, emp_id, old_salary, log_date)
VALUES ('DELETE', :OLD.employee_id, :OLD.salary, SYSDATE);
-- :NEW.salary는 NULL이므로 사용 불필요
END;
/
원인 3 해결: BEFORE 트리거에서만 :NEW 값 수정
:NEW 값의 수정은 반드시 BEFORE 트리거에서만 수행해야 합니다. AFTER 트리거에서 수정하려 하면 에러가 발생합니다.
잘못된 예시 (에러 발생):
-- AFTER 트리거에서 :NEW 값 변경 시도 → ORA-04076 발생
CREATE OR REPLACE TRIGGER trg_emp_after_salary
AFTER UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
-- AFTER 트리거에서는 :NEW 값 수정 불가!
:NEW.salary := :NEW.salary * 1.1; -- ORA-04076 발생
END;
/
올바른 예시 (BEFORE 트리거로 변경):
-- BEFORE 트리거에서 :NEW 값 수정 (정상 동작)
CREATE OR REPLACE TRIGGER trg_emp_before_salary
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
-- BEFORE 트리거에서는 :NEW 값 수정 가능
IF :NEW.salary > :OLD.salary * 2 THEN
-- 급여 인상이 2배를 초과하면 자동으로 2배로 제한
:NEW.salary := :OLD.salary * 2;
END IF;
END;
/
:OLD 읽기 전용 확인 예시:
-- :OLD는 읽기 전용이므로 아래 코드는 항상 에러 발생
CREATE OR REPLACE TRIGGER trg_bad_old_assign
BEFORE UPDATE ON employees
FOR EACH ROW
BEGIN
:OLD.salary := 5000; -- ORA-04076: :OLD는 수정 불가!
END;
/
-- :OLD 값은 오직 참조(읽기)만 가능합니다.
혼합 DML 이벤트에서 INSERTING/UPDATING/DELETING 조건자 활용
여러 DML 이벤트를 하나의 트리거에서 처리할 때는 조건자를 활용하여 안전하게 분기할 수 있습니다.
CREATE OR REPLACE TRIGGER trg_emp_dml_audit
AFTER INSERT OR UPDATE OR DELETE ON employees
FOR EACH ROW
DECLARE
v_action VARCHAR2(10);
BEGIN
IF INSERTING THEN
v_action := 'INSERT';
INSERT INTO audit_log (action, emp_id, new_salary, log_date)
VALUES (v_action, :NEW.employee_id, :NEW.salary, SYSDATE);
ELSIF UPDATING THEN
v_action := 'UPDATE';
INSERT INTO audit_log (action, emp_id, old_salary, new_salary, log_date)
VALUES (v_action, :NEW.employee_id, :OLD.salary, :NEW.salary, SYSDATE);
ELSIF DELETING THEN
v_action := 'DELETE';
INSERT INTO audit_log (action, emp_id, old_salary, log_date)
VALUES (v_action, :OLD.employee_id, :OLD.salary, SYSDATE);
END IF;
END;
/
예방 방법
1. 트리거 작성 전 :NEW/:OLD 사용 가능 여부 체크리스트 활용
트리거를 작성하기 전에 아래 규칙을 항상 확인하는 습관을 들이세요. ① 행 레벨 트리거(FOR EACH ROW)인지 확인, ② 트리거 이벤트(INSERT/UPDATE/DELETE)에 따라 :NEW와 :OLD의 유효성 확인, ③ :NEW 수정이 필요하다면 반드시 BEFORE 트리거인지 확인. 이 세 가지를 코드 리뷰 체크리스트에 포함시키는 것만으로도 ORA-04076의 대부분을 예방할 수 있습니다. 팀 내 트리거 작성 가이드라인 문서를 만들어 공유하면 더욱 효과적입니다.
2. 트리거 개발 시 단위 테스트 및 명명 규칙 적용
트리거를 개발할 때는 반드시 각 DML 이벤트(INSERT, UPDATE, DELETE)별로 단위 테스트를 수행하여 실제 데이터를 넣어 검증하세요. 또한 트리거 이름에 TRG_[테이블명]_[BEFORE/AFTER]_[DML종류] 형식의 명명 규칙을 적용하면 트리거의 목적과 컨텍스트가 이름에서 바로 드러나 실수를 줄일 수 있습니다. 예: TRG_EMPLOYEES_BEFORE_UPD, TRG_ORDERS_AFTER_INS. 개발 환경에서 충분히 테스트한 후 운영 환경에 배포하는 프로세스를 반드시 지켜야 합니다.
관련 에러
- ORA-04077:
WHEN절은 행 레벨 트리거에서만 사용 가능함을 나타내는 에러로, ORA-04076과 유사한 트리거 구조 오류입니다. - ORA-04079: 잘못된 트리거 명세(Invalid trigger specification)로, 트리거 정의 자체의 문법 오류 시 발생합니다.
- ORA-04098: 트리거가 유효하지 않거나 재컴파일에 실패한 경우 발생하며, 트리거 내 객체 변경 후 자주 나타납니다.
- ORA-04091: 변경 중인 테이블(Mutating Table) 에러로, 트리거 내에서 해당 트리거를 발생시킨 테이블을 조회하거나 수정하려 할 때 발생합니다.
- ORA-06512: PL/SQL 에러 스택 추적 에러로, 트리거 내 런타임 오류 발생 시 함께 표시되는 경우가 많습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.