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

ORA-00060
2026년 10월 24일 | DBMS Error 가이드

이 글에서 다루는 내용

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

ORA-00060 deadlock detected while waiting for resource 는?

ORA-00060은 두 개 이상의 세션이 서로 상대방이 보유한 리소스(행 락, 테이블 락 등)를 기다리며 교착 상태(Deadlock)에 빠졌을 때 Oracle이 자동으로 감지하여 발생시키는 에러입니다. Oracle은 내부적으로 주기적으로 교착 상태를 감지하며, 감지되는 순간 희생자(Victim) 세션을 선택하여 해당 트랜잭션을 강제 롤백시킵니다. 에러가 발생한 세션에는 ORA-00060 메시지가 전달되고, Oracle Alert Log와 트레이스 파일(udump 또는 diag/rdbms)에 교착 상태의 상세 정보가 기록됩니다.


주요 발생 원인

  • 서로 다른 순서로 행(Row)을 잠금 처리하는 로직

가장 흔한 원인으로, 두 트랜잭션이 동일한 테이블의 행들을 서로 다른 순서로 UPDATE 또는 DELETE할 때 발생합니다. 예를 들어, 세션 A가 행 1을 잠근 후 행 2를 잠그려 하고, 세션 B는 행 2를 잠근 후 행 1을 잠그려 할 때 두 세션이 서로를 기다리며 교착 상태가 형성됩니다. 이는 애플리케이션 설계 단계에서 잠금 순서를 통일하지 않은 경우 매우 자주 나타납니다.

  • 인덱스 부재로 인한 불필요한 테이블 풀 스캔 잠금

외래 키(Foreign Key) 컬럼에 인덱스가 없을 경우, 부모 테이블의 행을 삭제하거나 업데이트할 때 Oracle은 자식 테이블 전체에 공유 잠금(Share Lock)을 걸어 관련 행을 확인합니다. 이 과정에서 자식 테이블을 동시에 수정하려는 다른 세션과 충돌하여 교착 상태가 발생할 수 있습니다. 실무에서 간과하기 쉬운 원인 중 하나이며, 외래 키 인덱스 누락 여부를 정기적으로 점검해야 합니다.

  • 비효율적인 트랜잭션 설계 및 긴 트랜잭션 유지

하나의 트랜잭션 안에서 불필요하게 많은 행을 잠근 채 오랜 시간 커밋하지 않으면 다른 세션들이 해당 리소스를 기다리는 시간이 길어집니다. 특히 배치(Batch) 프로그램이나 대용량 업데이트 작업이 OLTP 세션과 동시에 실행될 경우, 잠금 경합이 심화되어 교착 상태로 이어질 가능성이 높아집니다. 트랜잭션 범위를 최소화하고 가능한 한 빠르게 커밋하는 것이 중요합니다.


해결 방법

원인 1: 잠금 순서 통일

모든 트랜잭션에서 동일한 테이블의 행을 처리할 때 반드시 기본 키(Primary Key) 오름차순 순서로 처리하도록 쿼리를 수정합니다.

-- 잘못된 예: 순서가 보장되지 않아 교착 상태 유발 가능
UPDATE orders SET status = 'PROCESSED' WHERE order_id IN (1002, 1001);

-- 올바른 예: ORDER BY로 일관된 잠금 순서 보장
UPDATE orders
SET    status = 'PROCESSED'
WHERE  order_id IN (
    SELECT order_id
    FROM   orders
    WHERE  order_id IN (1001, 1002)
    ORDER BY order_id  -- 항상 동일한 순서로 잠금
);

-- 교착 상태 발생 후 애플리케이션에서 재시도 처리 예시 (PL/SQL)
DECLARE
  v_retry NUMBER := 0;
BEGIN
  LOOP
    BEGIN
      UPDATE orders SET status = 'PROCESSED' WHERE order_id = 1001;
      UPDATE orders SET status = 'PROCESSED' WHERE order_id = 1002;
      COMMIT;
      EXIT; -- 성공 시 루프 종료
    EXCEPTION
      WHEN OTHERS THEN
        IF SQLCODE = -60 THEN  -- ORA-00060 교착 상태
          ROLLBACK;
          v_retry := v_retry + 1;
          IF v_retry >= 3 THEN
            RAISE;  -- 3회 재시도 후 예외 전파
          END IF;
          DBMS_LOCK.SLEEP(1);  -- 1초 대기 후 재시도
        ELSE
          RAISE;
        END IF;
    END;
  END LOOP;
END;
/

원인 2: 외래 키 인덱스 누락 확인 및 생성

아래 쿼리로 인덱스가 없는 외래 키를 찾아내고, 즉시 인덱스를 생성합니다.

-- 인덱스 없는 외래 키 컬럼 조회
SELECT a.table_name,
       a.constraint_name,
       a.columns,
       b.r_constraint_name
FROM (
    SELECT table_name,
           constraint_name,
           LISTAGG(column_name, ',') WITHIN GROUP (ORDER BY position) AS columns,
           r_constraint_name
    FROM   dba_cons_columns
    JOIN   dba_constraints USING (owner, table_name, constraint_name)
    WHERE  constraint_type = 'R'
    GROUP BY table_name, constraint_name, r_constraint_name
) a
WHERE NOT EXISTS (
    SELECT 1
    FROM   dba_ind_columns i
    WHERE  i.table_name = a.table_name
    AND    i.column_name = a.columns  -- 단일 컬럼 기준 (복합 컬럼은 추가 조정 필요)
)
ORDER BY a.table_name;

-- 인덱스 생성 예시
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
CREATE INDEX idx_order_items_order_id ON order_items(order_id);

원인 3: 현재 교착 상태 세션 확인 및 강제 종료

교착 상태 발생 시 Oracle이 자동으로 Victim을 선택하지만, 수동으로 확인하고 처리해야 할 경우 다음 쿼리를 활용합니다.

-- 현재 락 대기 중인 세션 확인
SELECT s.sid,
       s.serial#,
       s.username,
       s.status,
       s.sql_id,
       w.event,
       w.seconds_in_wait,
       l.type AS lock_type,
       l.mode_held,
       l.mode_requested
FROM   v$session s
JOIN   v$session_wait w ON s.sid = w.sid
JOIN   v$lock l ON s.sid = l.sid
WHERE  w.event LIKE '%enq%'
   OR  w.event = 'enq: TX - row lock contention'
ORDER BY w.seconds_in_wait DESC;

-- 교착 상태 유발 세션 강제 종료 (DBA 권한 필요)
ALTER SYSTEM KILL SESSION '142,3891' IMMEDIATE;

-- 트레이스 파일에서 교착 상태 정보 확인
SELECT value FROM v$diag_info WHERE name = 'Default Trace File';

예방 방법

  • 일관된 잠금 순서 정책 수립 및 코드 리뷰 강화

개발팀 전체에 “동일한 테이블의 여러 행을 처리할 때는 반드시 Primary Key 오름차순으로 처리한다”는 코딩 컨벤션을 명문화하고, 코드 리뷰 단계에서 이를 반드시 확인합니다. 또한 외래 키 생성 시 반드시 인덱스를 함께 생성하는 것을 개발 표준으로 정착시켜야 합니다. Oracle의 V$LOCK, V$SESSION, DBA_BLOCKERS, DBA_WAITERS 뷰를 활용한 모니터링 스크립트를 스케줄러에 등록하여 주기적으로 잠금 현황을 점검하는 것도 매우 효과적입니다.

  • 트랜잭션 최소화 및 애플리케이션 레벨 재시도 로직 구현

트랜잭션 범위를 최대한 작게 유지하고, 대용량 배치 작업은 OLTP 피크 타임을 피해 실행하도록 스케줄링합니다. 교착 상태는 완전히 제거하기 어려운 경우도 있으므로, 애플리케이션에서 ORA-00060 발생 시 자동으로 롤백 후 일정 횟수 재시도하는 로직을 반드시 구현해 두어야 합니다. Oracle 19c 이상에서는 DBMS_ERRLOG와 결합하여 에러 로깅 테이블에 교착 상태 이력을 남겨 추후 분석에 활용할 수 있습니다.


관련 에러

  • ORA-00054: resource busy and acquire with NOWAIT specified — DDL 실행 시 해당 객체에 락이 걸려 있을 때 발생하며, 교착 상태와 유사한 잠금 경합 상황에서 함께 나타나는 경우가 많습니다.
  • ORA-04021: timeout occurred while waiting to lock object — 오브젝트 레벨 잠금 타임아웃으로, 교착 상태보다 단순한 단방향 잠금 대기 상황에서 발생합니다.
  • ORA-04020: deadlock detected while trying to lock object — 딕셔너리 레벨(라이브러리 캐시) 교착 상태에서 발생하며, ORA-00060과는 발생 레이어가 다릅니다.
  • ORA-00057: maximum number of temporary table locks exceeded — 임시 테이블 잠금 한계 초과로, 대규모 병렬 처리 환경에서 교착 상태와 복합적으로 발생할 수 있습니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기