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

ORA-04020
2026년 08월 19일 | DBMS Error 가이드

이 글에서 다루는 내용

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

ORA-04020 deadlock detected while trying to lock object 는?

ORA-04020 에러는 Oracle 데이터베이스에서 두 개 이상의 세션이 서로 상대방이 보유한 객체 잠금(Object Lock)을 획득하려고 시도할 때 발생하는 교착 상태(Deadlock) 에러입니다. 이 에러는 주로 DDL 작업(테이블 컴파일, 패키지 재컴파일, 뷰 변경 등) 중에 발생하며, 일반적인 DML 레벨 교착 상태와는 다르게 라이브러리 캐시(Library Cache) 또는 딕셔너리 캐시(Dictionary Cache) 레벨에서의 잠금 충돌을 의미합니다. 운영 환경에서 배포 작업이나 스키마 변경 작업 중 여러 세션이 동시에 동일한 객체에 접근할 경우 빈번하게 나타날 수 있습니다.


주요 발생 원인

1. 동시 DDL 작업으로 인한 객체 잠금 충돌

두 개 이상의 세션이 동시에 같은 데이터베이스 객체(패키지, 프로시저, 트리거, 뷰 등)에 대해 컴파일 또는 변경 작업을 수행할 때 가장 많이 발생합니다. 예를 들어 세션 A가 패키지 BODY를 컴파일하는 동안, 세션 B가 해당 패키지가 참조하는 다른 객체를 변경하려 할 때 상호 대기 상태에 빠지게 됩니다. 배포 자동화 스크립트가 병렬로 실행되거나, 여러 DBA가 동시에 작업할 때 주로 목격되는 패턴입니다.

2. 무효화된 객체(INVALID Object)의 자동 재컴파일 충돌

Oracle은 의존성이 있는 객체가 변경되면 관련된 객체들을 자동으로 INVALID 상태로 만들고, 다음 호출 시 자동 재컴파일을 시도합니다. 여러 세션이 동시에 INVALID 상태인 같은 객체를 호출하면, 각 세션이 재컴파일 잠금을 획득하려는 과정에서 순환 대기가 발생할 수 있습니다. 특히 대형 패키지 계층 구조를 가진 시스템에서 배포 직후 트래픽이 유입될 때 집중적으로 발생합니다.

3. 분산 트랜잭션(Distributed Transaction) 또는 DB Link 사용 시 잠금 전파

DB Link를 통한 원격 데이터베이스 객체 접근 시, 로컬과 원격 양쪽에서 잠금이 발생하여 교착 상태로 이어질 수 있습니다. 특히 양방향 DB Link가 설정된 환경에서 양쪽 데이터베이스가 서로의 객체를 참조하는 트랜잭션을 동시에 실행할 경우, 잠금 체인이 네트워크를 넘어 형성되어 ORA-04020이 발생합니다. 이 경우 에러 감지 자체도 지연될 수 있어 원인 파악이 어렵습니다.


해결 방법

원인 1 해결: 동시 DDL 충돌 해소

현재 객체를 잠금 중인 세션을 조회하고, 필요시 강제 종료합니다.

-- 현재 라이브러리 캐시 잠금 현황 조회
SELECT
    s.sid,
    s.serial#,
    s.username,
    s.status,
    s.machine,
    lo.object AS locked_object,
    lo.type,
    lo.mode_held,
    lo.mode_requested
FROM
    v$session s,
    v$library_cache_lock lo
WHERE
    s.saddr = lo.session_id
ORDER BY
    s.sid;

-- 특정 객체를 잠금 중인 세션 확인
SELECT
    s.sid,
    s.serial#,
    s.username,
    s.program,
    do.object_name,
    do.object_type,
    lo.mode_held,
    lo.mode_requested
FROM
    v$session s
    JOIN v$library_cache_lock lo ON s.saddr = lo.session_id
    JOIN dba_objects do ON lo.object = do.object_id
WHERE
    do.object_name = 'YOUR_OBJECT_NAME'  -- 대상 객체명 입력
ORDER BY
    lo.mode_held DESC;

-- 잠금을 유발하는 세션 강제 종료 (주의: 운영 환경에서 신중히 사용)
ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE;
-- 예시: ALTER SYSTEM KILL SESSION '142,3891' IMMEDIATE;

원인 2 해결: INVALID 객체 사전 재컴파일

배포 후 트래픽 유입 전에 INVALID 객체를 수동으로 재컴파일하여 자동 재컴파일 충돌을 예방합니다.

-- INVALID 상태 객체 목록 조회
SELECT
    owner,
    object_name,
    object_type,
    status,
    last_ddl_time
FROM
    dba_objects
WHERE
    status = 'INVALID'
    AND owner NOT IN ('SYS', 'SYSTEM', 'OUTLN', 'DBSNMP')
ORDER BY
    object_type,
    object_name;

-- UTL_RECOMP 패키지를 사용한 스키마 내 일괄 재컴파일 (직렬 방식 권장)
BEGIN
    UTL_RECOMP.recomp_serial(schema => 'YOUR_SCHEMA_NAME');
END;
/

-- 병렬 재컴파일 (CPU 코어 수에 맞게 조정, 단 교착 위험 있으므로 주의)
BEGIN
    UTL_RECOMP.recomp_parallel(
        threads => 4,
        schema  => 'YOUR_SCHEMA_NAME'
    );
END;
/

-- 특정 객체 수동 재컴파일
ALTER PACKAGE your_schema.your_package COMPILE;
ALTER PACKAGE your_schema.your_package COMPILE BODY;
ALTER PROCEDURE your_schema.your_procedure COMPILE;
ALTER VIEW your_schema.your_view COMPILE;

-- 스크립트로 전체 INVALID 객체 재컴파일 SQL 생성
SELECT
    'ALTER ' || object_type || ' ' || owner || '.' || object_name || ' COMPILE;' AS recompile_sql
FROM
    dba_objects
WHERE
    status = 'INVALID'
    AND object_type IN ('PACKAGE', 'PACKAGE BODY', 'PROCEDURE', 'FUNCTION', 'TRIGGER', 'VIEW')
    AND owner = 'YOUR_SCHEMA_NAME'
ORDER BY
    CASE object_type
        WHEN 'PACKAGE' THEN 1
        WHEN 'PACKAGE BODY' THEN 2
        WHEN 'PROCEDURE' THEN 3
        WHEN 'FUNCTION' THEN 4
        WHEN 'TRIGGER' THEN 5
        WHEN 'VIEW' THEN 6
    END;

원인 3 해결: DB Link 관련 잠금 해소

-- 현재 활성 분산 트랜잭션 확인
SELECT
    d.local_tran_id,
    d.global_tran_id,
    d.state,
    d.mixed,
    d.host,
    d.db_link,
    s.sid,
    s.username
FROM
    dba_2pc_pending d
    LEFT JOIN v$session s ON d.local_tran_id = s.taddr;

-- 고아 분산 트랜잭션 강제 롤백 (DBA 권한 필요)
ROLLBACK FORCE 'local_tran_id_value';
-- 예시: ROLLBACK FORCE '1.21.3457';

-- DB Link 세션 현황 확인
SELECT
    s.sid,
    s.serial#,
    s.username,
    s.server,
    s.osuser,
    s.program,
    s.sql_id
FROM
    v$session s
WHERE
    s.server = 'PSEUDO'  -- DB Link 사용 세션 식별
ORDER BY
    s.sid;

예방 방법

1. 단일 배포 창(Deployment Window) 운영 및 순차적 DDL 실행

운영 환경에서 DDL 작업은 반드시 정해진 배포 창 내에서, 단일 세션으로 순차적으로 수행해야 합니다. 배포 스크립트에서 각 DDL 문 사이에 충분한 대기 시간(DBMS_LOCK.SLEEP)을 부여하고, 배포 완료 후 UTL_RECOMP.recomp_serial()을 실행하여 모든 의존 객체를 재컴파일한 뒤 서비스 트래픽을 전환하는 절차를 표준화하는 것이 핵심입니다.

-- 배포 후 재컴파일 및 검증 표준 스크립트 예시
BEGIN
    -- 1단계: 직렬 재컴파일 수행
    UTL_RECOMP.recomp_serial(schema => 'APP_SCHEMA');
    
    -- 2단계: 재컴파일 후 INVALID 객체 잔존 여부 확인
    FOR rec IN (
        SELECT object_name, object_type
        FROM dba_objects
        WHERE status = 'INVALID'
          AND owner = 'APP_SCHEMA'
    ) LOOP
        DBMS_OUTPUT.PUT_LINE('STILL INVALID: ' || rec.object_type || ' - ' || rec.object_name);
    END LOOP;
END;
/

2. DDL_LOCK_TIMEOUT 파라미터 설정으로 무한 대기 방지

DDL_LOCK_TIMEOUT 파라미터를 적절히 설정하면, DDL 작업이 잠금 획득에 실패할 경우 무한정 대기하지 않고 지정된 시간 후 에러를 반환하게 할 수 있습니다. 이를 통해 교착 상태가 장기간 지속되는 상황을 방지하고, 운영팀이 신속하게 상황을 인지하고 대응할 수 있습니다.

-- 세션 레벨 DDL 잠금 타임아웃 설정 (초 단위, 0 = 즉시 실패, 기본값은 무제한 대기)
ALTER SESSION SET DDL_LOCK_TIMEOUT = 30;

-- 시스템 레벨 설정 (모든 세션에 적용)
ALTER SYSTEM SET DDL_LOCK_TIMEOUT = 30 SCOPE = BOTH;

-- 현재 파라미터 값 확인
SELECT name, value, description
FROM v$parameter
WHERE name = 'ddl_lock_timeout';

관련 에러

  • ORA-00060: 일반 DML 레벨 교착 상태(Deadlock). ORA-04020이 DDL/라이브러리 캐시 레벨 교착 상태라면, ORA-00060은 행(Row) 레벨 잠금에서의 교착 상태입니다.
  • ORA-04021: timeout occurred while waiting to lock object. ORA-04020과 유사하나, 교착 상태 대신 설정된 타임아웃 시간 초과로 발생합니다.
  • ORA-00604: error occurred at recursive SQL level. ORA-04020 발생 시 함께 나타나는 경우가 많으며, Oracle 내부 재귀 SQL 수행 중 에러를 의미합니다.
  • ORA-02049: timeout: distributed transaction waiting for lock. DB Link 환경의 분산 트랜잭션에서 잠금 대기 타임아웃 시 발생하며, ORA-04020의 분산 트랜잭션 케이스와 연관됩니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기