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

ORA-04089
2026년 08월 26일 | DBMS Error 가이드

이 글에서 다루는 내용

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

ORA-04089 cannot create triggers on objects owned by SYS 는?

ORA-04089는 SYS 사용자가 소유한 객체(테이블, 뷰 등)에 트리거를 생성하려고 할 때 발생하는 Oracle 에러입니다. Oracle은 데이터베이스 보안 및 안정성을 보호하기 위해 SYS 스키마 소유 객체에 대한 트리거 생성을 근본적으로 차단하고 있습니다. 이 에러는 주로 DBA가 감사(Audit) 목적이나 데이터 변경 추적을 위해 시스템 딕셔너리 테이블에 트리거를 붙이려 할 때 발생하며, SYS로 직접 로그인하거나 다른 사용자가 SYS 소유 객체에 트리거를 생성하려 해도 동일하게 발생합니다.


주요 발생 원인

  • SYS 소유 딕셔너리 테이블에 트리거 직접 생성 시도

가장 흔한 원인으로, DBA가 SYS 계정으로 접속하여 DBA_TABLES, USER$, OBJ$ 등 SYS 소유의 내부 딕셔너리 테이블에 트리거를 생성하려는 경우입니다. Oracle은 데이터 딕셔너리의 무결성을 보호하기 위해 이를 원천적으로 막아두었으며, 이는 Oracle 8i 이후 버전부터 적용된 보안 정책입니다. SYS 계정으로 로그인하여 아래와 같이 트리거를 생성하면 즉시 에러가 발생합니다.

“`sql

— SYS 계정으로 접속 후 시도 시 ORA-04089 발생

CONNECT SYS/password AS SYSDBA;

CREATE OR REPLACE TRIGGER sys_audit_trigger

BEFORE INSERT OR UPDATE OR DELETE ON SYS.USER$

FOR EACH ROW

BEGIN

— 감사 로깅 목적

DBMS_OUTPUT.PUT_LINE(‘변경 감지: ‘ || :OLD.NAME);

END;

/

— ERROR: ORA-04089: cannot create triggers on objects owned by SYS

“`

  • 다른 사용자 계정에서 SYS 소유 객체에 트리거 생성 시도

SYS가 아닌 일반 사용자 계정(예: SYSTEM, DBADMIN 등)이 SYS 소유 객체에 트리거를 생성하려는 경우에도 동일한 에러가 발생합니다. 많은 DBA들이 SYSTEM 계정은 SYS와 비슷한 권한을 가지고 있다고 오해하여 이 시도를 하지만, 객체의 소유자(Owner)가 SYS인 한 어떤 사용자도 해당 객체에 트리거를 생성할 수 없습니다.

“`sql

— SYSTEM 계정으로 접속 후 SYS 소유 테이블에 트리거 생성 시도

CONNECT SYSTEM/password;

CREATE OR REPLACE TRIGGER monitor_users

AFTER INSERT ON SYS.DBA_USERS — SYS 소유 뷰

FOR EACH ROW

BEGIN

INSERT INTO SYSTEM.AUDIT_LOG (event_time, action)

VALUES (SYSDATE, ‘NEW USER CREATED’);

END;

/

— ERROR: ORA-04089: cannot create triggers on objects owned by SYS

“`

  • 스크립트 또는 마이그레이션 중 객체 소유자 혼동

데이터베이스 마이그레이션, 복구 작업, 또는 타 DB에서 가져온 DDL 스크립트를 실행할 때 특정 테이블의 소유자가 SYS인 경우를 인지하지 못하고 트리거를 생성하려 하는 경우입니다. 특히 소유자를 명시적으로 지정하지 않거나, 스크립트가 SYS 컨텍스트에서 실행될 때 이 에러가 빈번하게 발생합니다.

“`sql

— 객체 소유자 확인 쿼리 (트리거 생성 전 반드시 확인)

SELECT OWNER, OBJECT_NAME, OBJECT_TYPE

FROM DBA_OBJECTS

WHERE OBJECT_NAME = ‘YOUR_TABLE_NAME’

AND OBJECT_TYPE IN (‘TABLE’, ‘VIEW’);

— 예시 결과: OWNER가 SYS이면 트리거 생성 불가

— OWNER | OBJECT_NAME | OBJECT_TYPE

— SYS | USER$ | TABLE

“`


해결 방법

해결책 1: 대안 객체(Wrapper Table 또는 사용자 소유 객체) 활용

SYS 객체를 직접 모니터링해야 한다면, SYS 테이블을 직접 참조하는 대신 사용자 소유의 테이블이나 뷰를 만들어 해당 객체에 트리거를 생성하는 방법을 사용합니다.

-- 1단계: 일반 사용자로 모니터링용 테이블 생성
CONNECT DBA_USER/password;

CREATE TABLE dba_user.user_change_log (
    log_id       NUMBER GENERATED ALWAYS AS IDENTITY,
    changed_at   DATE DEFAULT SYSDATE,
    change_type  VARCHAR2(10),
    user_name    VARCHAR2(128),
    changed_by   VARCHAR2(128)
);

-- 2단계: 트리거를 SYS 객체가 아닌 사용자 소유 테이블에 생성
-- (모니터링 대상을 직접 추적하는 대신 애플리케이션 레이어 활용)
CREATE OR REPLACE TRIGGER trg_user_change_log
AFTER INSERT OR UPDATE OR DELETE ON dba_user.app_users
FOR EACH ROW
BEGIN
    INSERT INTO dba_user.user_change_log
        (change_type, user_name, changed_by)
    VALUES (
        CASE
            WHEN INSERTING THEN 'INSERT'
            WHEN UPDATING THEN 'UPDATE'
            ELSE 'DELETE'
        END,
        :NEW.username,
        SYS_CONTEXT('USERENV', 'SESSION_USER')
    );
END;
/

해결책 2: Oracle Auditing(감사) 기능 활용

SYS 객체에 대한 변경을 추적하려는 목적이라면, 트리거 대신 Oracle의 내장 감사 기능을 활용하는 것이 올바른 접근 방법입니다.

-- 통합 감사(Unified Auditing) 정책 생성 (Oracle 12c 이상)
CONNECT SYS/password AS SYSDBA;

CREATE AUDIT POLICY sys_object_changes
ACTIONS INSERT, UPDATE, DELETE
ON SYS.USER$;

-- 감사 정책 활성화
AUDIT POLICY sys_object_changes;

-- 감사 결과 조회
SELECT EVENT_TIMESTAMP, DB_USERNAME, ACTION_NAME, OBJECT_NAME
FROM UNIFIED_AUDIT_TRAIL
WHERE OBJECT_SCHEMA = 'SYS'
ORDER BY EVENT_TIMESTAMP DESC;

-- 구버전(Traditional Auditing, Oracle 11g 이하)
AUDIT INSERT, UPDATE, DELETE ON SYS.USER$ BY ACCESS;

-- 감사 결과 조회 (전통 감사)
SELECT USERNAME, TIMESTAMP, ACTION_NAME, OBJ_NAME
FROM DBA_AUDIT_TRAIL
WHERE OBJ_NAME = 'USER$'
ORDER BY TIMESTAMP DESC;

해결책 3: DDL 트리거(Database-Level Trigger) 활용

데이터베이스 레벨의 DDL 이벤트를 감시하려는 경우, SYS 소유 객체에 직접 트리거를 거는 대신 Database-Level DDL 트리거를 활용할 수 있습니다.

-- 데이터베이스 레벨 DDL 트리거 생성 (SYS 객체 트리거 우회)
CONNECT SYS/password AS SYSDBA;

CREATE TABLE system.ddl_audit_log (
    log_id      NUMBER GENERATED ALWAYS AS IDENTITY,
    event_date  DATE DEFAULT SYSDATE,
    event_type  VARCHAR2(30),
    object_type VARCHAR2(30),
    object_name VARCHAR2(128),
    db_user     VARCHAR2(128),
    client_ip   VARCHAR2(64)
);

CREATE OR REPLACE TRIGGER db_ddl_audit_trigger
AFTER DDL ON DATABASE
BEGIN
    INSERT INTO system.ddl_audit_log
        (event_type, object_type, object_name, db_user, client_ip)
    VALUES (
        ORA_SYSEVENT,
        ORA_DICT_OBJ_TYPE,
        ORA_DICT_OBJ_NAME,
        ORA_LOGIN_USER,
        SYS_CONTEXT('USERENV', 'IP_ADDRESS')
    );
    COMMIT;
EXCEPTION
    WHEN OTHERS THEN
        NULL; -- DDL 작업 자체를 방해하지 않도록 예외 처리
END;
/

예방 방법

  • 트리거 생성 전 객체 소유자 사전 검증 프로세스 수립

트리거를 생성하기 전에 반드시 대상 객체의 소유자를 확인하는 검증 쿼리를 개발 표준으로 채택하세요. 특히 마이그레이션 스크립트나 자동화 배포 파이프라인에서는 아래 쿼리를 사전 검증 단계에 포함시켜 SYS 소유 객체에 대한 트리거 생성 시도 자체를 차단하는 것이 좋습니다.

“`sql

— 트리거 생성 전 소유자 확인 표준 쿼리

SELECT

o.OWNER,

o.OBJECT_NAME,

o.OBJECT_TYPE,

CASE

WHEN o.OWNER = ‘SYS’ THEN ‘⚠️ 트리거 생성 불가 (SYS 소유)’

ELSE ‘✅ 트리거 생성 가능’

END AS trigger_eligibility

FROM DBA_OBJECTS o

WHERE o.OBJECT_NAME = UPPER(‘&target_object_name’)

AND o.OBJECT_TYPE IN (‘TABLE’, ‘VIEW’);

“`

  • SYS 계정 직접 접속 제한 및 대안 아키텍처 설계

실무에서 SYS 계정으로 직접 개발 작업을 수행하는 것은 절대 지양해야 합니다. 감사, 모니터링, 변경 추적이 필요한 경우에는 항상 사용자 정의 스키마에 래퍼(Wrapper) 테이블이나 뷰를 생성하고, Oracle의 Unified Auditing 또는 Fine-Grained Auditing(FGA) 기능을 활용하는 아키텍처를 설계하세요. 이 방식은 ORA-04089뿐만 아니라 다른 SYS 관련 권한 에러도 예방할 수 있습니다.


관련 에러

  • ORA-04088: 트리거 실행 중 오류가 발생했을 때 표시되는 에러로, 트리거 내부 로직 오류와 관련됩니다.
  • ORA-04095: 동일한 이름의 트리거가 이미 존재할 때 발생하며, CREATE OR REPLACE 구문으로 해결 가능합니다.
  • ORA-01031: 트리거 생성에 필요한 충분한 권한이 없을 때 발생하며, CREATE TRIGGER 또는 CREATE ANY TRIGGER 권한 부여로 해결합니다.
  • ORA-04021: 객체에 대한 LOCK 대기 타임아웃 에러로, 트리거 컴파일 시 대상 객체가 잠긴 경우 발생합니다.
  • ORA-00604: SYS 레벨의 재귀 SQL 오류로, 잘못 설계된 시스템 트리거로 인해 유발될 수 있습니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기