Oracle Lock Session이란? 발생 원인과 대처, Wait Event 확인 방법

반응형
Oracle Lock Session이란? 발생 원인과 대처, Wait Event 확인 방법

Oracle Lock Session이란? 발생 원인과 대처, Wait Event 확인 방법

Oracle에서 “lock session”은 보통 다른 세션이 잡고 있는 락(또는 래치/뮤텍스/엔큐) 때문에 내 세션이 WAITING 상태로 오래 머무는 상황을 의미합니다. 운영 환경에서는 “느려졌다”로 시작해 결국 Blocking Session(차단 세션)을 찾는 흐름으로 이어지는 경우가 많습니다.

Lock Session의 핵심 개념

요약
- 락을 잡고 있는 세션 = Blocker(Blocking Session)
- 락 때문에 기다리는 세션 = Waiter(Waiting Session)
- 대부분의 “락 대기”는 TX(로우 락) 또는 TM(테이블 락)로 대표됩니다.

Oracle은 동시성을 위해 다양한 동기화 메커니즘을 씁니다. 현장에서 “lock session”이라고 부를 때는 보통 아래 중 하나를 의미합니다.

  • enq: TX - row lock contention: 같은 로우(또는 FK 관련) 갱신/삭제 충돌로 대기
  • enq: TM - contention: 테이블 레벨의 DML 락(주로 FK 미인덱스/DDL과 충돌 등)
  • library cache lock / pin: SQL/PLSQL 객체 컴파일/파싱 등 라이브러리 캐시 경합
  • buffer busy waits: 핫 블록(ITL/프리스페이스 등) 경합
  • RAC 환경: gc buffer busy, gc current block busy 등 글로벌 캐시 관련 대기

실무 기준으로 보면 “lock session”을 해결하는 첫 단추는 waiter만 보지 말고 blocker를 먼저 특정하는 것입니다.

Lock Session 발생 시 대처 방법

대응은 “진단 → 안전 확인 → 조치” 순서로 가는 게 사고를 줄입니다. 특히 무작정 kill session은 롤백 부담과 2차 장애를 부를 수 있어 절차가 중요합니다.

1) 차단 세션(Blocker)부터 찾기

가장 먼저 waiter와 blocker를 매핑합니다(11g+ 기준으로 단순/명확).

-- Waiter/Blocker 빠르게 보기
SELECT
  s.sid,
  s.serial#,
  s.username,
  s.status,
  s.event,
  s.seconds_in_wait,
  s.blocking_session AS blocker_sid,
  s.sql_id,
  s.machine,
  s.program
FROM v$session s
WHERE s.blocking_session IS NOT NULL
ORDER BY s.seconds_in_wait DESC;

Blocker 세션의 SQL과 트랜잭션 상태를 확인합니다.

-- Blocker의 현재/직전 SQL 확인(간단 버전)
SELECT
  s.sid, s.serial#, s.username, s.status,
  s.sql_id, s.prev_sql_id,
  s.event, s.state,
  s.machine, s.program
FROM v$session s
WHERE s.sid = :blocker_sid;

-- SQL 텍스트 확인
SELECT sql_id, child_number, sql_text
FROM v$sql
WHERE sql_id = (SELECT sql_id FROM v$session WHERE sid = :blocker_sid);

2) “진짜 락”인지, “다른 대기”인지 구분

event가 TX/TM 계열이면 락 가능성이 높고, library cache / buffer busy / gc 계열이면 원인이 달라집니다. 아래는 세션 대기 상세(파라미터 p1/p2/p3 포함)를 보는 기본 쿼리입니다.

-- 세션의 현재 대기 상세
SELECT
  sw.sid,
  sw.event,
  sw.state,
  sw.wait_class,
  sw.seconds_in_wait,
  sw.p1text, sw.p1,
  sw.p2text, sw.p2,
  sw.p3text, sw.p3
FROM v$session_wait sw
WHERE sw.sid = :waiter_sid;

3) 조치 옵션(상황별 우선순위)

권장 흐름
1) 앱/배치가 정상 종료 가능한지 먼저 확인(커밋/롤백 유도)
2) 장기 트랜잭션이면 영향 범위(롤백 시간/언두/락 범위) 계산
3) 불가피할 때만 kill session(즉시/강제 종료는 마지막 카드)

A. 애플리케이션 레벨에서 해소(가장 안전)

  • 문제 트랜잭션을 커밋/롤백하도록 유도
  • 배치/스케줄러라면 해당 작업을 중지하고 정상 종료
  • 사용자 화면/프로세스가 멈춘 경우 연결 상태(네트워크/세션 유실) 확인

B. DB 레벨에서 세션 종료

세션 종료 전 아래를 확인합니다. (1) blocker가 정말 문제의 원인인지, (2) 롤백 부담이 큰지, (3) 업무 영향이 어디까지인지.

-- 일반 종료(권장)
ALTER SYSTEM KILL SESSION ':sid,:serial' IMMEDIATE;

-- RAC면 인스턴스 포함
ALTER SYSTEM KILL SESSION ':sid,:serial,@:inst_id' IMMEDIATE;

IMMEDIATE는 “바로 사라진다”가 아니라 세션을 종료 요청하고, 남은 작업은 롤백이 진행될 수 있습니다. 대형 트랜잭션이면 “kill 이후 롤백 때문에 더 느려지는” 현상도 가능합니다.

C. 재발 방지(개발/설계 관점)

  • 자주 갱신되는 테이블은 짧은 트랜잭션으로 쪼개기
  • 동시 업데이트 충돌이 잦으면 업데이트 순서/키 설계 통일
  • 필요 시 NOWAIT / SKIP LOCKED 활용
  • FK 관련 TM 대기가 있으면 FK 인덱스 점검(미인덱스 FK는 대표적인 락 확대 원인)
  • DML과 DDL 동시 수행 최소화(배포/인덱스 작업 시간대 분리)
-- 락을 기다리지 않게(즉시 실패)
SELECT * FROM your_table WHERE ... FOR UPDATE NOWAIT;

-- 락 걸린 로우는 건너뛰고 처리(큐/배치에 유용)
SELECT * FROM your_table WHERE ... FOR UPDATE SKIP LOCKED;

Lock Session 확장: Wait Event 설명 및 확인하는 방법

“확장 확인”의 핵심은 현재(실시간) + 과거(구간)를 함께 보는 것입니다. 실시간은 v$session_wait, 과거 구간은 ASH(Active Session History)가 가장 실무적입니다.

1) 대표 Wait Event 해석(락/경합 중심)

enq: TX - row lock contention

로우 레벨 DML 충돌이 대표적입니다. 같은 로우 갱신, 또는 FK 제약 관련 충돌(특히 부모/자식 삭제/갱신 흐름)에서 자주 나옵니다.

enq: TM - contention

테이블 락 경합. FK 미인덱스, DDL과 DML 충돌, 특정 DML이 테이블 레벨 락을 강하게 잡는 경우에 주의합니다.

library cache lock / pin

파싱/컴파일/객체 재정의 등으로 공유 풀에서 경합이 생길 때. “락”처럼 보이지만 원인은 SQL 공유/DDL/컴파일일 수 있습니다.

buffer busy waits

핫 블록 경합(ITL 부족, 프리스페이스, 세그먼트/인덱스 특정 블록 집중). 인덱스/테이블 설계와 트랜잭션 패턴을 같이 봐야 합니다.

2) 실시간 확인: 어떤 이벤트로 얼마나 기다리나

-- 특정 세션이 무엇을 기다리는지(실시간)
SELECT
  s.sid, s.serial#, s.username,
  s.status, s.state,
  s.event, s.wait_class,
  s.seconds_in_wait,
  s.blocking_session,
  s.sql_id,
  s.machine, s.program
FROM v$session s
WHERE s.sid = :sid;

-- 현재 wait 파라미터(p1/p2/p3)까지
SELECT
  sw.sid, sw.event, sw.state, sw.wait_class,
  sw.seconds_in_wait,
  sw.p1text, sw.p1,
  sw.p2text, sw.p2,
  sw.p3text, sw.p3
FROM v$session_wait sw
WHERE sw.sid = :sid;

3) 확장 확인(구간 분석): ASH로 “언제부터, 어떤 SQL이, 누구에게 막혔나”

ASH 라이선스/설정에 따라 사용 가능 여부가 다를 수 있지만, 가능하다면 가장 빠르게 그림이 나옵니다.

-- 최근 N분 동안 세션 활동(대기 이벤트/차단자/SQL)
SELECT
  sample_time,
  session_id AS sid,
  session_serial# AS serial#,
  sql_id,
  event,
  wait_class,
  blocking_session,
  blocking_session_serial#,
  time_waited/1000000 AS time_waited_sec
FROM v$active_session_history
WHERE sample_time >= SYSDATE - (10/1440)  -- 최근 10분
  AND session_id = :sid
ORDER BY sample_time;

-- 최근 N분 동안 "락/엔큐" 위주로 상위 이벤트 집계
SELECT
  event,
  COUNT(*) AS samples
FROM v$active_session_history
WHERE sample_time >= SYSDATE - (10/1440)
  AND event LIKE 'enq:%'
GROUP BY event
ORDER BY samples DESC;

4) 차단 세션을 객체 단위로 좁히기(락 객체/테이블 추적)

락은 결국 “어떤 객체(테이블/인덱스/로우)”에 걸렸는지로 좁혀야 재발 방지가 됩니다.

-- 세션이 잡고 있거나 기다리는 락 확인(객체까지 매핑)
SELECT
  s.sid,
  s.serial#,
  s.username,
  l.type,
  l.lmode,
  l.request,
  l.block,
  o.owner,
  o.object_name,
  o.object_type
FROM v$session s
JOIN v$lock l
  ON l.sid = s.sid
LEFT JOIN dba_objects o
  ON o.object_id = l.id1
WHERE s.sid IN (:waiter_sid, :blocker_sid)
ORDER BY s.sid, l.type;

-- blocker/waiter 관계를 락 키 기준으로 매핑(고급)
SELECT
  w.sid AS waiter_sid,
  b.sid AS blocker_sid,
  w.type,
  w.id1, w.id2
FROM v$lock w
JOIN v$lock b
  ON w.type = b.type
 AND w.id1  = b.id1
 AND w.id2  = b.id2
WHERE w.request > 0   -- 기다리는 쪽
  AND b.block = 1;    -- 막는 쪽

TX/TM 계열은 id1/id2 해석이 필요할 수 있고 버전에 따라 접근 방식이 달라집니다. 하지만 현업에서는 먼저 blocking_session + sql_id + dba_objects 조합으로도 대부분 방향이 잡힙니다.

현장 체크리스트

바로 적용 순서
1) v$session에서 blocking_session 확인 → blocker SID 확보
2) blocker의 sql_id / 프로그램 / 사용자 / 머신 확인
3) event가 TX/TM인지, library/buffer/gc인지 구분
4) 업무 영향/롤백 부담 확인 후 정상 종료 유도 → 최후에 kill session
5) 원인 패턴(긴 트랜잭션, FK 인덱스, DDL 동시 수행, 핫 블록)을 찾아 재발 방지 반영

실제 사용 시에는 “대기 세션만” 보고 조급해지기 쉽지만, 관리자 입장에서는 차단 세션의 정체와 작업 내용이 먼저 정리되어야 안전한 조치가 가능합니다.

반응형