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

ORA-04094
2026년 08월 27일 | DBMS Error 가이드

이 글에서 다루는 내용

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

ORA-04094 cannot ROLLBACK in a trigger 는?

ORA-04094 에러는 Oracle 데이터베이스에서 트리거(Trigger) 내부에 ROLLBACK 문을 직접 사용하려 할 때 발생하는 에러입니다. Oracle은 트리거가 실행되는 동안 트랜잭션 제어 구문(TCL: Transaction Control Language) 중 ROLLBACK을 허용하지 않으며, 이는 트리거가 호출한 DML 문과 동일한 트랜잭션 컨텍스트 안에서 동작하기 때문입니다. 실무에서는 트리거 안에서 오류 처리 로직을 구현하면서 무의식적으로 ROLLBACK을 호출하거나, 다른 DBMS에서 마이그레이션된 코드에 해당 구문이 포함되어 있을 때 자주 발생합니다.


주요 발생 원인

1. 트리거 본문에 명시적 ROLLBACK 구문 사용

가장 흔한 원인으로, 트리거 내부에서 예외 처리(EXCEPTION) 블록 또는 일반 실행 블록에 ROLLBACK; 구문을 직접 작성한 경우입니다. Oracle 트리거는 부모 트랜잭션에 종속되어 동작하므로, 트리거 내에서 독립적으로 트랜잭션을 롤백하는 것이 아키텍처적으로 허용되지 않습니다. 트리거가 실행되는 시점에서 이미 열려 있는 트랜잭션을 임의로 종료시키면 데이터 일관성이 무너질 수 있기 때문입니다.

2. 트리거 내에서 ROLLBACK을 포함하는 저장 프로시저(Stored Procedure) 호출

트리거 본문에 직접 ROLLBACK을 쓰지 않더라도, 트리거 내에서 호출하는 프로시저나 패키지 내부에 ROLLBACK 구문이 포함되어 있으면 동일한 에러가 발생합니다. 이 경우에는 에러의 원인을 추적하기가 어렵기 때문에, 의존성 체인 전체를 검토해야 합니다. 특히 공통 유틸리티 프로시저에 트랜잭션 제어 구문이 숨어 있는 경우가 많아 주의가 필요합니다.

3. 다른 DBMS(MySQL, SQL Server 등)에서 마이그레이션된 코드

MySQL이나 SQL Server와 같은 타 DBMS는 트리거 내에서 트랜잭션 롤백을 허용하는 경우가 있어, 해당 코드를 Oracle로 그대로 이식하면 ORA-04094가 발생합니다. 마이그레이션 과정에서 자동화 도구를 사용할 경우 이 차이를 자동으로 처리하지 못하는 경우가 많습니다. Oracle의 트랜잭션 모델은 타 DBMS와 근본적으로 다르므로, 마이그레이션 후 반드시 수동 검토가 필요합니다.


해결 방법

원인 1 해결: ROLLBACK 제거 후 예외 발생(RAISE_APPLICATION_ERROR) 사용

트리거 내에서 ROLLBACK 대신 RAISE_APPLICATION_ERROR를 사용하면, 트리거를 호출한 DML 문 전체가 자동으로 롤백됩니다. Oracle은 처리되지 않은 예외가 트리거에서 발생하면 해당 트랜잭션 전체를 자동으로 취소하는 메커니즘을 가지고 있습니다.

-- 잘못된 예시 (ORA-04094 발생)
CREATE OR REPLACE TRIGGER trg_check_salary
BEFORE INSERT OR UPDATE ON employees
FOR EACH ROW
BEGIN
  IF :NEW.salary < 0 THEN
    ROLLBACK; -- 이 구문이 ORA-04094를 유발
  END IF;
END;
/

-- 올바른 예시: RAISE_APPLICATION_ERROR 사용
CREATE OR REPLACE TRIGGER trg_check_salary
BEFORE INSERT OR UPDATE ON employees
FOR EACH ROW
BEGIN
  IF :NEW.salary < 0 THEN
    RAISE_APPLICATION_ERROR(-20001, '급여는 0보다 작을 수 없습니다.');
    -- 예외 발생 시 Oracle이 자동으로 트랜잭션을 롤백함
  END IF;
END;
/

원인 2 해결: 호출 프로시저에서 ROLLBACK 제거 또는 분리

트리거에서 호출하는 프로시저 내의 ROLLBACK을 제거하거나, 해당 프로시저를 트리거 전용 버전과 일반 버전으로 분리하는 것이 바람직합니다.

-- 문제가 있는 프로시저 (트리거에서 호출 시 ORA-04094 발생)
CREATE OR REPLACE PROCEDURE proc_log_error(p_msg IN VARCHAR2) IS
BEGIN
  INSERT INTO error_log (message, log_date) VALUES (p_msg, SYSDATE);
  ROLLBACK; -- 트리거 컨텍스트에서는 허용 불가
END;
/

-- 해결책: ROLLBACK 제거, 예외 전파 방식으로 변경
CREATE OR REPLACE PROCEDURE proc_log_error_for_trigger(p_msg IN VARCHAR2) IS
  PRAGMA AUTONOMOUS_TRANSACTION; -- 독립 트랜잭션 사용
BEGIN
  INSERT INTO error_log (message, log_date) VALUES (p_msg, SYSDATE);
  COMMIT; -- 자율 트랜잭션 내에서는 COMMIT/ROLLBACK 허용
EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK; -- 자율 트랜잭션 내부이므로 허용
    RAISE;
END;
/

-- 위 프로시저를 트리거에서 안전하게 호출
CREATE OR REPLACE TRIGGER trg_audit_employees
AFTER INSERT OR UPDATE OR DELETE ON employees
FOR EACH ROW
BEGIN
  proc_log_error_for_trigger('employees 테이블 변경 감지');
END;
/

원인 3 해결: 마이그레이션 코드 수동 수정

타 DBMS에서 이식된 트리거 코드를 Oracle 스타일로 변환합니다.

-- MySQL 스타일 (마이그레이션 전, Oracle에서는 동작 불가)
-- SIGNAL을 사용한 롤백 유발 패턴 → Oracle 방식으로 변환 필요

-- Oracle 스타일로 변환된 트리거
CREATE OR REPLACE TRIGGER trg_validate_order
BEFORE INSERT ON orders
FOR EACH ROW
DECLARE
  v_customer_status VARCHAR2(10);
BEGIN
  SELECT status INTO v_customer_status
  FROM customers
  WHERE customer_id = :NEW.customer_id;

  IF v_customer_status = 'BLOCKED' THEN
    -- ROLLBACK 대신 예외 발생으로 트랜잭션 취소
    RAISE_APPLICATION_ERROR(-20002, '차단된 고객은 주문할 수 없습니다. 고객 ID: ' || :NEW.customer_id);
  END IF;
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    RAISE_APPLICATION_ERROR(-20003, '존재하지 않는 고객 ID입니다: ' || :NEW.customer_id);
END;
/

예방 방법

1. 트리거 코딩 표준 수립 및 코드 리뷰 프로세스 적용

팀 내 Oracle 트리거 작성 가이드라인을 문서화하고, 트리거 내에서 ROLLBACK, COMMIT, SAVEPOINT 등의 TCL 구문 사용을 명시적으로 금지하는 규칙을 코드 리뷰 체크리스트에 포함시켜야 합니다. 자동화된 정적 분석 도구(예: PL/SQL Cop, SonarQube Oracle 플러그인)를 CI/CD 파이프라인에 통합하면 배포 전 단계에서 해당 문제를 사전에 차단할 수 있습니다.

2. 로깅 및 감사 목적 트리거에는 반드시 AUTONOMOUS_TRANSACTION 사용

트리거 내에서 별도의 로그 테이블에 데이터를 저장하거나 감사 기록을 남기는 경우, PRAGMA AUTONOMOUS_TRANSACTION을 선언한 별도의 프로시저를 통해 처리해야 합니다. 이를 통해 주 트랜잭션의 성공 여부와 관계없이 로그를 독립적으로 커밋할 수 있으며, 트랜잭션 제어 구문과 관련된 에러를 원천적으로 방지할 수 있습니다.


관련 에러

  • ORA-04092: COMMIT은 트리거 내에서 사용할 수 없음을 나타내는 에러로, ORA-04094와 쌍을 이루는 대표적인 트리거 제약 에러입니다.
  • ORA-04091: 트리거가 자신이 속한 테이블을 변경(mutating)하려 할 때 발생하는 에러로, 트리거 설계 오류와 자주 함께 나타납니다.
  • ORA-00600: 트리거 내 잘못된 트랜잭션 처리가 내부 오류로 전파될 경우 드물게 연관될 수 있습니다.
  • ORA-04095: 동일 테이블에 동일 시점(BEFORE/AFTER)으로 이미 트리거가 존재할 때 발생하며, 복잡한 트리거 구조 설계 시 함께 고려해야 합니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기