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

ORA-30006
2026년 10월 07일 | DBMS Error 가이드

이 글에서 다루는 내용

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

ORA-30006 resource busy; acquire with WAIT timeout expired 는?

ORA-30006 에러는 Oracle 데이터베이스에서 특정 리소스(주로 락, Lock)를 획득하려 할 때 지정된 대기 시간(WAIT timeout) 내에 해당 리소스를 획득하지 못했을 경우 발생하는 에러입니다. 주로 LOCK TABLE ... WAIT n 구문이나 DML 문장에서 WAIT 옵션을 사용할 때, 다른 세션이 이미 해당 리소스를 점유하고 있어 지정한 초(second) 내에 락을 얻지 못하면 이 에러가 발생합니다. 실무에서는 동시 접근이 빈번한 트랜잭션 환경에서 자주 마주치게 되며, 적절히 처리하지 않으면 애플리케이션 장애로 이어질 수 있습니다.


주요 발생 원인

1. 장기 미커밋 트랜잭션(Long-running Uncommitted Transaction)에 의한 락 점유

가장 흔한 원인으로, 특정 세션이 DML(INSERT, UPDATE, DELETE) 작업 후 COMMIT 또는 ROLLBACK을 수행하지 않은 채 장시간 대기 상태에 놓이는 경우입니다. 이 상태에서 다른 세션이 동일한 로우(Row) 또는 테이블에 접근하여 WAIT 옵션으로 락 획득을 시도하면, 타임아웃이 만료되어 ORA-30006이 발생합니다. 특히 배치 작업이나 대용량 업데이트 중 네트워크 단절 또는 클라이언트 비정상 종료가 발생했을 때 이 상황이 자주 나타납니다.

2. 잘못된 LOCK TABLE … WAIT 구문 사용 또는 짧은 타임아웃 설정

애플리케이션 코드에서 LOCK TABLE 테이블명 IN EXCLUSIVE MODE WAIT n 구문을 사용할 때 n 값을 너무 짧게 설정하는 경우입니다. 예를 들어 WAIT 값을 1~2초로 설정했는데 락을 보유한 세션의 트랜잭션이 미처 끝나지 않으면 에러가 발생합니다. 또한, 테이블 레벨 락이 필요하지 않은 상황에서 불필요하게 LOCK TABLE 구문을 사용하는 설계 문제도 이에 해당합니다.

3. 데드락(Deadlock) 또는 락 경합(Lock Contention)이 심한 환경

여러 세션이 서로 다른 리소스를 점유한 채 상대방의 리소스를 기다리는 데드락 상황, 혹은 동일 리소스에 대한 접근 요청이 폭증하는 고경합 환경에서도 ORA-30006이 발생할 수 있습니다. 데드락의 경우 Oracle이 자동으로 하나의 희생자(Victim)를 선정해 ORA-00060을 발생시키지만, WAIT 타임아웃이 먼저 만료되면 ORA-30006이 먼저 나타날 수도 있습니다. 접속자가 많은 OLTP 환경에서 특히 주의가 필요합니다.


해결 방법

원인 1 해결: 블로킹 세션 확인 및 종료

먼저 현재 어떤 세션이 락을 점유하고 있는지 확인합니다.

-- 락을 보유한 세션과 대기 중인 세션 확인
SELECT
    l.sid             AS blocking_sid,
    s.serial#         AS blocking_serial,
    s.username        AS blocking_user,
    s.status          AS blocking_status,
    s.machine         AS blocking_machine,
    s.program         AS blocking_program,
    q.sql_text        AS blocking_sql,
    w.sid             AS waiting_sid,
    w.event           AS wait_event,
    w.seconds_in_wait AS wait_seconds
FROM
    v$lock l
    JOIN v$session s ON l.sid = s.sid
    JOIN v$session w ON w.blocking_session = l.sid
    LEFT JOIN v$sql q ON s.sql_id = q.sql_id
WHERE
    l.block = 1
ORDER BY
    w.seconds_in_wait DESC;

블로킹 세션을 확인한 후, 해당 세션을 강제로 종료합니다.

-- 블로킹 세션 강제 종료 (DBA 권한 필요)
ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE;

-- 예시
ALTER SYSTEM KILL SESSION '145,3892' IMMEDIATE;

미커밋 트랜잭션 여부도 함께 확인합니다.

-- 미커밋 트랜잭션 확인
SELECT
    t.addr,
    t.xidusn,
    t.xidslot,
    t.xidsqn,
    t.status,
    t.start_time,
    s.sid,
    s.serial#,
    s.username,
    s.osuser,
    s.machine
FROM
    v$transaction t
    JOIN v$session s ON t.ses_addr = s.saddr
ORDER BY
    t.start_time;

원인 2 해결: LOCK TABLE 구문 검토 및 타임아웃 조정

불필요한 테이블 레벨 락을 제거하거나, WAIT 값을 현실적으로 조정합니다.

-- 잘못된 예 (너무 짧은 WAIT)
LOCK TABLE orders IN EXCLUSIVE MODE WAIT 1;

-- 개선된 예 (적절한 WAIT 또는 NOWAIT 사용 후 예외 처리)
BEGIN
    -- NOWAIT 사용 후 예외 처리로 유연하게 대응
    LOCK TABLE orders IN EXCLUSIVE MODE NOWAIT;
    -- 이후 DML 수행
    UPDATE orders SET status = 'PROCESSED' WHERE order_date < SYSDATE - 30;
    COMMIT;
EXCEPTION
    WHEN OTHERS THEN
        IF SQLCODE = -54 THEN
            -- ORA-00054: resource busy (NOWAIT 시 발생)
            DBMS_OUTPUT.PUT_LINE('테이블이 사용 중입니다. 잠시 후 재시도하세요.');
            ROLLBACK;
        ELSIF SQLCODE = -30006 THEN
            -- ORA-30006: WAIT timeout 만료
            DBMS_OUTPUT.PUT_LINE('락 획득 타임아웃. 관리자에게 문의하세요.');
            ROLLBACK;
        ELSE
            RAISE;
        END IF;
END;
/

원인 3 해결: 락 경합 상세 분석 및 쿼리 튜닝

-- 현재 대기 이벤트 및 락 경합 현황 파악
SELECT
    sw.sid,
    sw.event,
    sw.wait_class,
    sw.seconds_in_wait,
    sw.state,
    s.username,
    s.sql_id,
    q.sql_text
FROM
    v$session_wait sw
    JOIN v$session s ON sw.sid = s.sid
    LEFT JOIN v$sql q ON s.sql_id = q.sql_id
WHERE
    sw.wait_class != 'Idle'
ORDER BY
    sw.seconds_in_wait DESC;

-- 특정 객체에 대한 락 현황 상세 조회
SELECT
    lo.oracle_username,
    lo.os_user_name,
    lo.process,
    lo.locked_mode,
    do.object_name,
    do.object_type,
    s.sid,
    s.serial#,
    s.status,
    s.machine
FROM
    v$locked_object lo
    JOIN dba_objects do ON lo.object_id = do.object_id
    JOIN v$session s ON lo.session_id = s.sid
ORDER BY
    lo.locked_mode DESC;

인덱스가 없어 Full Table Scan이 발생하고 락 범위가 넓어지는 경우라면, 적절한 인덱스를 추가합니다.

-- 락 경합을 유발하는 테이블에 인덱스 추가 예시
CREATE INDEX idx_orders_status_date
    ON orders(status, order_date)
    PARALLEL 4;

-- 인덱스 생성 후 통계 갱신
EXEC DBMS_STATS.GATHER_TABLE_STATS(
    ownname => 'SCOTT',
    tabname => 'ORDERS',
    cascade => TRUE
);

예방 방법

1. 트랜잭션을 최대한 짧고 명확하게 유지하기 (Short Transaction Design)

모든 DML 작업은 가능한 짧은 트랜잭션 단위로 설계하고, 작업 완료 즉시 COMMIT 또는 ROLLBACK을 수행하는 습관을 들여야 합니다. 특히 사용자 입력을 기다리는 동안 트랜잭션을 열어두는 “Hold Transaction” 패턴은 반드시 피해야 하며, 애플리케이션 레벨에서 Connection Pool의 트랜잭션 타임아웃을 설정하여 비정상 종료 시 자동으로 롤백되도록 구성해야 합니다. 또한 배치 작업의 경우 일정 건수마다 중간 COMMIT을 수행하여 락 보유 시간을 최소화하세요.

2. 정기적인 락 모니터링 및 알림 체계 구축

운영 환경에서는 락 경합이 발생했을 때 즉시 감지할 수 있도록 Oracle Enterprise Manager(OEM) 또는 커스텀 모니터링 스크립트를 활용한 알림 체계를 구축해야 합니다. 아래와 같은 스크립트를 주기적으로 실행하여 일정 시간 이상 대기 중인 세션을 조기에 감지하고 조치할 수 있습니다.

-- 30초 이상 락 대기 중인 세션 알림용 쿼리 (모니터링 스크립트)
SELECT
    s.sid,
    s.serial#,
    s.username,
    s.machine,
    s.program,
    sw.event,
    sw.seconds_in_wait,
    s.blocking_session AS blocked_by
FROM
    v$session s
    JOIN v$session_wait sw ON s.sid = sw.sid
WHERE
    s.blocking_session IS NOT NULL
    AND sw.seconds_in_wait > 30
ORDER BY
    sw.seconds_in_wait DESC;

이 쿼리를 DBMS_SCHEDULER 잡(Job)으로 등록하여 주기적으로 실행하고, 결과가 있을 경우 DBA에게 이메일 또는 알림을 발송하도록 구성하면 사전 대응이 가능합니다.


관련 에러

  • ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired — NOWAIT 옵션 사용 시 즉시 락을 얻지 못할 경우 발생하는 에러로, ORA-30006과 가장 유사한 에러입니다. WAIT 옵션 유무의 차이가 있습니다.
  • ORA-00060: deadlock detected while waiting for resource — 두 개 이상의 세션이 서로의 리소스를 기다리는 데드락 상황에서 Oracle이 자동으로 발생시키는 에러입니다.
  • ORA-04021: timeout occurred while waiting to lock object — 딕셔너리 객체(예: 패키지, 프로시저)에 대한 락 획득 타임아웃 시 발생합니다.
  • ORA-02049: timeout: distributed transaction waiting for lock — 분산 트랜잭션(Distributed Transaction) 환경에서 락 대기 타임아웃이 발생할 때 나타나는 에러입니다.
DBMS 에러 코드 시리즈

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

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

댓글 남기기