본문 바로가기
IT

ORA-00060 deadlock detected while waiting for resource — 데드락 검출 에러 원인·해결·예방"

by 샤나엘 2026. 5. 22.
반응형

ORA-00060은 두 개 이상의 세션이 서로가 보유한 자원을 기다리며 영원히 진행할 수 없는 순환 대기 상태에 빠졌을 때 발생한다. 오라클은 이 상태를 자동으로 검출해 한 세션의 현재 SQL 문 하나만 롤백시키고 다른 세션은 진행하도록 처리한다. 데드락은 동시성 시스템의 본질적 부산물이며, 외래키 인덱스 누락·반대 순서 갱신·ITL 슬롯 부족 같은 구조적 원인이 결합돼 발생한다. 본 글은 ORA-00060의 정확한 의미, Oracle의 자동 검출 메커니즘, 일곱 가지 원인, 트레이스 파일 분석법, 그리고 접근 순서 표준화부터 INITRANS 튜닝까지 실전 해결 패턴을 정리한다.

ORA-00060

한눈에 보기
에러 의미 / 두 세션 이상이 서로 자원을 기다리며 순환 대기에 빠짐 (Oracle 자동 검출 + 희생자 statement 롤백)
주요 원인 / 반대 순서 갱신, FK 인덱스 누락, 비트맵 인덱스, PK 충돌 INSERT, ITL 슬롯 부족, 트리거 캐스케이드, 장시간 트랜잭션
근본 해법 / 접근 순서 표준화, FK 인덱스 생성, INITRANS 증가, SELECT FOR UPDATE 사전 락, 짧은 트랜잭션, 재시도

목차

01ORA-00060 정확한 의미와 Oracle 자동 검출
02발생 원인 일곱 가지
03재현 시나리오 — 두 세션 반대 순서 UPDATE
04트레이스 파일 분석과 Deadlock Graph 해석
05TX 락 vs TM 락 — 어디서 충돌하는가
06해결 방법 — 순서 표준화·FK 인덱스·INITRANS·재시도
07모니터링 쿼리 — V$LOCK, V$SESSION_WAIT, AWR
Q&A자주 묻는 질문 5선
08예방 체크리스트와 결론
01 / ORA-00060과 Oracle 자동 검출
오라클 공식 메시지는 "deadlock detected while waiting for resource"이며, 한국어로는 "자원을 기다리는 중에 교착 상태가 검출되었습니다"로 표시된다.

오라클은 데드락을 자동으로 검출하는 메커니즘을 갖고 있다. 락 대기 사이클이 형성되면 마지막에 사이클을 닫게 되는 세션이 검출 시점에 이를 스스로 발견하고, 자신의 현재 실행 중인 SQL 문 하나만 statement-level rollback시킨다. 이 점이 중요하다 ━ 전체 트랜잭션이 롤백되는 것이 아니라 데드락을 유발한 SQL 한 문장만 롤백된다. 희생자 세션은 살아 있고 그 안의 다른 변경 내역은 그대로 남으므로, 애플리케이션이 COMMIT 또는 ROLLBACK을 의식적으로 결정해야 한다. 의식하지 않으면 부분적으로만 적용된 트랜잭션이 남아 데이터 부정합을 일으킨다.

1. 데드락이란

데드락은 두 개 이상의 세션이 서로 다른 세션이 보유한 락을 기다리는 순환 의존성이다. 가장 단순한 사이클은 다음과 같다.

  • 세션 A: 행 1 UPDATE → 행 1에 TX 락 X 모드 보유
  • 세션 B: 행 2 UPDATE → 행 2에 TX 락 X 모드 보유
  • 세션 A: 행 2 UPDATE → 세션 B 락 대기
  • 세션 B: 행 1 UPDATE → 세션 A 락 대기

이 시점에서 두 세션 모두 영원히 진행 불가능하다. 오라클은 보통 3초 이내에 사이클을 검출하고 한쪽을 끊는다.

2. 발생 원인 일곱 가지

원인 1 / 반대 순서 갱신 (가장 고전적)

같은 테이블의 같은 행 집합을 두 세션이 서로 다른 순서로 갱신할 때 발생한다. 위 예시가 바로 이 패턴이다.

원인 2 / 외래키 인덱스 누락

자식 테이블의 FK 컬럼에 인덱스가 없으면 부모에서 DELETE 또는 PK UPDATE 시 자식 테이블 전체에 TM 락이 걸린다. 이 TM 락이 동시 DML과 충돌해 데드락을 만든다. 오라클 환경에서 가장 빈번한 데드락 원인이다.

원인 3 / 고DML 테이블의 비트맵 인덱스

비트맵 인덱스는 하나의 비트맵 항목이 다수 행을 커버한다. 고DML 환경에서는 서로 다른 행을 갱신해도 같은 비트맵 항목에 락이 걸려 충돌이 발생한다. OLTP 테이블에는 비트맵 인덱스를 사용하지 않는 것이 원칙이다.

원인 4 / PK 충돌 동시 INSERT

같은 PK 값을 가진 행을 두 세션이 동시에 INSERT하려고 하면, 첫 세션이 락을 잡고 두 번째 세션이 enq: TX - row lock contention 대기에 빠진다. 다른 락과 결합되면 데드락이 된다.

원인 5 / ITL(Interested Transaction List) 슬롯 부족

오라클의 각 데이터 블록은 ITL이라는 슬롯 배열을 갖는다. 한 블록에 동시 접근 가능한 트랜잭션 수는 ITL 슬롯 수만큼이다. 기본값은 INITRANS(테이블 생성 시 지정, 보통 1~2)이며 부족하면 슬롯 확보를 위해 대기한다. 핫 블록에서 슬롯 확보 대기 중 사이클이 형성되면 데드락이 발생한다.

원인 6 / 트리거 캐스케이드

AFTER UPDATE 트리거가 다른 테이블을 갱신할 때, 메인 SQL과 트리거가 잡는 락의 순서가 일관되지 않으면 데드락이 발생한다. 특히 여러 트리거가 서로 다른 테이블을 갱신하면 순서가 꼬이기 쉽다.

원인 7 / 장시간 트랜잭션

트랜잭션이 길어질수록 락 보유 시간이 늘어나고 다른 세션과 충돌할 확률이 높아진다. 잦은 COMMIT을 빼먹은 배치 잡이 흔한 사례다.

3. 재현 시나리오

두 세션이 같은 두 행을 반대 순서로 갱신하면 정확히 재현된다.

-- 준비
CREATE TABLE accounts (id NUMBER PRIMARY KEY, bal NUMBER);
INSERT INTO accounts VALUES (1, 1000);
INSERT INTO accounts VALUES (2, 1000);
COMMIT;

-- 세션 1
UPDATE accounts SET bal = bal - 100 WHERE id = 1;

-- 세션 2
UPDATE accounts SET bal = bal - 50 WHERE id = 2;

-- 세션 1 (블록됨)
UPDATE accounts SET bal = bal + 100 WHERE id = 2;

-- 세션 2 (블록 → 오라클 데드락 검출)
UPDATE accounts SET bal = bal + 50 WHERE id = 1;
-- ORA-00060: deadlock detected while waiting for resource

이때 오라클은 한쪽 세션을 희생자로 선택하고 해당 UPDATE만 롤백한다. 다른 세션은 진행할 수 있다.

4. 트레이스 파일 분석

ORA-00060 발생 시 오라클은 자동으로 트레이스 파일을 생성한다. 위치는 $ORACLE_BASE/diag/rdbms/<db_name>/<sid>/trace/ 디렉토리이며 파일명에 _ora_ 패턴이 포함된다.

-- 현재 세션의 트레이스 파일 경로
SELECT value
FROM   v$diag_info
WHERE  name = 'Default Trace File';

-- alert log 위치
SELECT value
FROM   v$diag_info
WHERE  name = 'Diag Alert';

트레이스 파일 내부에는 Deadlock Graph 섹션이 있다. 형식은 다음과 같다.

Deadlock graph:
                       ---------Blocker(s)--------  ---------Waiter(s)---------
Resource Name          process session holds waits  process session holds waits
TX-0009001e-00000abc        35      22 X              40      31       X
TX-000a0021-00000def        40      31 X              35      22       X

session 22: DID 0001-0023-...  session 31: DID 0001-0028-...
session 31: DID 0001-0028-...  session 22: DID 0001-0023-...

Rows waited on:
  Session 22: obj - rowid = ... (ACCOUNTS)
  Session 31: obj - rowid = ... (ACCOUNTS)

해석 포인트는 다음과 같다.

  • Resource Name: TX-xxxxyyyy-zzzzwwww 형식. TX는 트랜잭션 락, 뒤 숫자는 USN·slot·sequence
  • holds / waits: 보유 모드와 대기 모드. X(6)=Exclusive, S(4)=Share, SX(3)=Sub-Exclusive
  • Rows waited on: 충돌이 발생한 정확한 행의 객체·rowid

이 정보로 어떤 두 SQL이 어떤 행에서 충돌했는지 즉시 파악할 수 있다. 대부분의 경우 SQL 한 줄을 수정하거나 인덱스 하나를 추가하는 것으로 해결된다.

디버깅 흐름
ORA-00060이 발생하면 다음 순서로 확인한다. 첫째, V$DIAG_INFO로 트레이스 파일 위치를 찾고 Deadlock Graph 섹션을 추출한다. 둘째, Resource Name이 TX면 행 락, TM이면 테이블 락이다. 셋째, Rows waited on에 표시된 객체·rowid로 충돌 행을 식별한다. 넷째, 그 객체가 외래키를 가진 자식 테이블이면 FK 인덱스 누락을 의심한다. 다섯째, 트레이스 끝의 current SQL 두 개를 비교해 접근 순서 차이가 있는지 본다.

5. TX 락과 TM 락

오라클의 락 종류는 여럿이지만 데드락의 주역은 TX와 TM이다.

  • TX (Transaction Enqueue) ━ 행 단위 트랜잭션 락. 어떤 트랜잭션이 변경한 행을 다른 트랜잭션이 변경하려 할 때 발생. ID1은 USN<<16|slot 형식, ID2는 wrap# 형식
  • TM (DML Enqueue) ━ 테이블 단위 메타데이터 락. DML 시 자동 획득. FK 인덱스 누락 시 자식 테이블 전체에 발생하는 락이 바로 이것

락 모드는 다음과 같다.

모드 코드 설명 전형적 발생
0 None 없음
1 NULL 내부 상태
2 SS (Row-S) 행 공유 LOCK TABLE IN ROW SHARE MODE
3 SX (Row-X) 행 배타 INSERT/UPDATE/DELETE, SELECT FOR UPDATE 시 TM
4 S (Share) 공유 LOCK TABLE IN SHARE MODE, FK 인덱스 누락 부모 DELETE
5 SSX 공유+행 배타
6 X (Exclusive) 배타 행 변경 시 TX, DDL 시 TM

6. 해결 방법

패턴 A / 접근 순서 표준화

같은 자원 집합을 다루는 모든 코드가 동일한 순서로 락을 획득하도록 규칙을 정한다. 가장 흔한 규칙은 "항상 ID 오름차순으로 처리"다. 이 한 가지만으로도 데드락의 절반 이상이 사라진다.

패턴 B / FK 컬럼 인덱스 생성

오라클 환경에서 데드락의 가장 흔한 원인은 FK 인덱스 누락이다. 모든 자식 테이블의 FK 컬럼에 인덱스를 생성한다.

-- FK 컬럼 인덱스
CREATE INDEX ix_emp_dept_id ON emp(dept_id);

-- 인덱스 누락 FK 일괄 색출
SELECT c.table_name, c.constraint_name, cc.column_name
FROM   user_constraints  c
JOIN   user_cons_columns cc ON c.constraint_name = cc.constraint_name
WHERE  c.constraint_type = 'R'
  AND NOT EXISTS (
    SELECT 1 FROM user_ind_columns i
    WHERE  i.table_name      = cc.table_name
      AND  i.column_name     = cc.column_name
      AND  i.column_position = cc.position
  );

패턴 C / INITRANS 증가

핫 테이블에서 ITL 슬롯 부족이 의심되면 INITRANS를 늘린다. 기본값 2 정도를 16 또는 그 이상으로 증가시키는 것이 권장된다. 단, 기존 블록에 반영되려면 테이블 MOVE가 필요하다.

-- 핫 테이블 ITL 슬롯 확장
ALTER TABLE orders INITRANS 16;
ALTER TABLE orders MOVE;

-- MOVE 후 인덱스는 UNUSABLE 상태가 되므로 재빌드
ALTER INDEX ix_orders_xxx REBUILD;

패턴 D / SELECT ... FOR UPDATE로 사전 락

여러 행을 락 잡아야 하는 트랜잭션이라면 시작 시점에 SELECT FOR UPDATE로 한꺼번에 정해진 순서로 락을 잡는다. 이 방식은 모든 락을 즉시 결정하므로 사이클 형성을 막는다.

-- 정해진 순서로 사전 락
SELECT *
FROM   accounts
WHERE  id IN (1, 2)
ORDER BY id
FOR UPDATE;

-- 이제 안전하게 UPDATE
UPDATE accounts SET bal = bal - 100 WHERE id = 1;
UPDATE accounts SET bal = bal + 100 WHERE id = 2;
COMMIT;

패턴 E / 애플리케이션 재시도

데드락은 동시성의 본질적 부산물이다. 완전히 차단할 수 없는 케이스도 있으므로 ORA-00060을 catch해 잠시 대기 후 재시도하는 패턴을 표준화한다.

// Java 예시 - SQLException SQLState 61000 또는 errorCode 60 검사
int retry = 0;
while (retry < 3) {
  try {
    executeBusinessTx();
    break;
  } catch (SQLException e) {
    if (e.getErrorCode() == 60) {
      Thread.sleep(50 * (int) Math.pow(2, retry));
      retry++;
    } else {
      throw e;
    }
  }
}

7. 모니터링 쿼리

-- 현재 블로킹 락 조회
SELECT s.sid, s.serial#, s.username, s.machine,
       l.type, l.lmode, l.request, o.object_name
FROM   v$lock     l
JOIN   v$session  s ON l.sid = s.sid
LEFT JOIN dba_objects o ON l.id1 = o.object_id
WHERE  l.block > 0;

-- 락 대기 이벤트
SELECT sid, event, p1, p2, p3, seconds_in_wait
FROM   v$session_wait
WHERE  event LIKE 'enq: TX%'
   OR event LIKE 'enq: TM%';

-- 과거 데드락 추적 (AWR, Enterprise Edition 필요)
SELECT sample_time, session_id, sql_id,
       event, blocking_session
FROM   dba_hist_active_sess_history
WHERE  event = 'enq: TX - row lock contention'
  AND  sample_time > SYSDATE - 1
ORDER BY sample_time DESC;
Q & A — 자주 묻는 다섯 가지
Q1. ORA-00060이 발생하면 전체 트랜잭션이 롤백되나요?
아니다. 오라클은 희생자 세션의 현재 SQL 문 하나만 statement-level rollback한다. 트랜잭션의 다른 변경 내역은 그대로 남는다. 따라서 애플리케이션은 ORA-00060을 받은 후 의식적으로 COMMIT 또는 ROLLBACK을 호출해야 한다. 누락하면 부분 변경이 남아 데이터 부정합을 일으킨다.

Q2. 어떤 세션이 희생자로 선택되나요?
일반적으로 데드락 사이클을 가장 늦게 닫은 세션이 희생자가 된다. 즉 마지막 락 요청을 한 세션이다. 다만 오라클 내부 알고리즘에 의존하므로 완전히 예측 가능하지는 않다. 우선순위가 높은 트랜잭션이 희생당할 수도 있다는 점을 가정하고 재시도 로직을 설계해야 한다.

Q3. 트레이스 파일이 너무 많아 식별이 어렵습니다.
ORA-00060 트레이스는 파일명에 "ora_"가 포함되며 발생 시각이 파일명에 반영된다. 가장 최근 파일부터 timestamp로 정렬해 "Deadlock graph"를 grep하면 즉시 찾을 수 있다. alert log에도 데드락 발생과 트레이스 경로가 함께 기록되므로 alert log 추적이 더 빠르다.

Q4. INITRANS를 증가시키면 성능에 부정적 영향은 없나요?
ITL 슬롯 자체는 블록 내 24바이트씩 차지한다. INITRANS 16이면 384바이트가 슬롯에 할애된다. 데이터 저장 공간이 그만큼 줄어 블록당 행 수가 약간 감소할 수 있지만, 핫 테이블에서 ITL 대기로 인한 성능 저하에 비하면 손실은 미미하다. 일반 OLTP 테이블이라면 영향이 거의 없다.

Q5. 데드락을 완전히 차단할 수 있나요?
완전한 차단은 동시성을 포기하는 것과 같다. 모든 데이터 변경을 직렬화하면 데드락은 사라지지만 처리량도 사라진다. 현실적 목표는 (1) 구조적 데드락(FK 인덱스 누락, 반대 순서)을 제거하고 (2) 우발적 데드락에 대해 짧은 재시도로 대응하는 것이다. 운영 시스템에서 분당 데드락 0건이 아니라, 데드락 발생 시 1초 이내 자동 복구되는 상태를 목표로 한다.

8. 예방 체크리스트와 결론

ORA-00060은 데이터베이스 설계와 애플리케이션 코딩 양쪽에서 동시성을 의식적으로 다룰 때 거의 차단할 수 있다. 새 모듈·새 트랜잭션 패턴을 설계할 때 다음 항목을 점검한다.

예방 체크리스트
  • 동일 자원 집합 접근 순서를 애플리케이션 전체에서 표준화 (ID 오름차순 권장)
  • 모든 FK 컬럼에 인덱스 생성 (데드락 절반 이상이 여기서 해결)
  • OLTP 테이블에서 비트맵 인덱스 사용 금지 (B-Tree 사용)
  • 핫 테이블 INITRANS 16 이상으로 설정 후 MOVE로 반영
  • 여러 행 락이 필요한 트랜잭션은 SELECT FOR UPDATE로 사전 락 + ORDER BY
  • 트랜잭션 길이 단축, 잦은 COMMIT (단 중간 COMMIT의 의미를 의식)
  • 애플리케이션에 ORA-00060 catch + 지수 백오프 재시도 표준화
  • V$LOCK, V$SESSION_WAIT, DBA_HIST_ASH 기반 락 경합 정기 점검

ORA-00060은 데이터베이스가 동시성을 진지하게 다루고 있다는 신호다. 완전히 사라지지는 않지만, 구조적 원인(FK 인덱스, 접근 순서)을 제거하고 우발적 발생에 대한 재시도를 갖춰 두면 운영에 위협이 되지 않는 수준으로 관리할 수 있다. 데드락은 "두 트랜잭션이 서로의 발목을 잡고 있다"는 단순한 그림이지만, 그 뒤에는 인덱스 설계·트랜잭션 길이·접근 순서·블록 구조까지 시스템 전체의 동시성 모델이 깔려 있다. 한 줄로 요약하면 ━ 동시 접근 순서를 의식적으로 결정하라. 그것이 ORA-00060 예방의 본질이다.

 

#ORA00060 #deadlock #데드락 #오라클에러 #락경합 #FK인덱스 #ITL #INITRANS #TX락 #TM락 #트레이스 #V$LOCK #동시성 #트랜잭션 #데이터무결성

반응형

댓글