데이터베이스 연구실.log

잠금과 공간 경합

TM 락과 DDL 및 외래키 충돌, 동시 INSERT의 HW 경합과 인스턴스별 UNDO의 US 경합

ALLOCATE EXTENTT_PAYMENTCOMMIT WRITENOWAITORA-00060TX lockenq: TM - contentionTM 락ORA-00054ddl_lock_timeout외래키 인덱스enq: HW - contentionHWMASSMMSSMenq: US - contentionORA-30036undo_retentionRETENTION GUARANTEE

#핵심 요약

enqueue 보호 대상 대기 이벤트
TX 두 트랜잭션의 같은 행 변경 enq: TX - row lock contention(세션과 블로킹 진단)
TM 테이블 단위 DML/DDL 호환성, 외래키 무결성 enq: TM - contention
HW 같은 세그먼트의 HWM 이동과 공간 확장 enq: HW - contention
US Undo 세그먼트 할당, 온라인 전환, 확장 enq: US - contention

DML은 대상 테이블에 Row Exclusive(모드 3) TM 락을 자동으로 잡음
다른 DML과는 호환되지만 DROP, TRUNCATE, ALTER와 배타 테이블 락(모드 6)과는 충돌함

자식 테이블의 외래키 컬럼에 인덱스가 없으면, 부모 행을 지울 때 자식의 미커밋 DML과 TM 경합이 생김
실습에서는 이것을 현업의 주요 TM 경합 원인으로 설명했음

여러 세션, 노드가 같은 테이블에 대량 INSERT하면 HWM 확장 요청이 몰림
ASSM, 익스텐트 사전 확보, 노드별 파티션 적재로 완화함

RAC에서 데이터 파일은 공유하지만 Undo 테이블스페이스는 인스턴스별로 따로 씀
한 인스턴스의 Undo가 부족하면 그 인스턴스에 접속한 업무만 실패할 수 있음

#개념과 동작 원리

#TM 락 충돌 유형

상황 충돌 조건 결과
DML 중 DDL DDL의 배타 락과 진행 중 DML의 TM 락 ddl_lock_timeout = 0(기본)이면 즉시 ORA-00054
값을 주면 그 초만큼 기다린 뒤 실패
테이블 전체 배타 락 LOCK TABLE ... IN EXCLUSIVE MODE(모드 6) 보유 중 다른 세션 DML enq: TM - contention 대기
인덱스 없는 외래키 부모 키 삭제, 변경 때 필요한 강한 TM 락과 자식 미커밋 DML TM 대기

#HWM과 공간 관리

HWM(High-Water Mark)은 지금까지 사용된 적 있는 가장 높은 블록의 경계임
INSERT로 기존 공간이 부족하면 HWM을 올리거나 새 익스텐트를 확보하고, 이 공간 관리 정보 갱신이 HW enqueue로 직렬화됨
RAC에서는 공간 관리 블록이 노드 사이를 오가며 추가 경합이 생길 수 있음

방식 설정 동시 INSERT
MSSM(수동) SEGMENT SPACE MANAGEMENT MANUAL, PCTFREE, PCTUSED, FREELISTS 프리리스트 하나에 INSERT가 몰리면 경합 증가
ASSM(자동) SEGMENT SPACE MANAGEMENT AUTO, 비트맵으로 블록 여유 공간 관리 여러 블록으로 분산되어 유리
높은 동시 INSERT에서는 확장 경합이 남을 수 있음

실습에서는 ASSM을 19c 기본값으로 설명했음
세그먼트 공간 회수는 세그먼트 공간 회수에 있음

#Undo와 US enqueue

워크로드가 급증하면 Undo 세그먼트 수가 자동으로 늘고 새 세그먼트가 온라인으로 바뀌는데, 이때 동시 요청이 몰리면 US 경합이 생길 수 있음
Undo를 더 확장할 수 없으면 ORA-30036으로 DML이 실패함
Undo의 용도(롤백, 읽기 일관성, Flashback, 인스턴스 복구)와 보존 기간은 언두 보존에 있음

파라미터 설명
undo_management AUTO 권장
undo_tablespace 인스턴스마다 다르게(예: <INSTANCE1>은 UNDOTBS1, <INSTANCE2>는 UNDOTBS2)
undo_retention 커밋된 Undo를 최소 몇 초 보관할지
최장 쿼리 실행 시간 기준
실습 환경 기본값 900

주의: undo_retention을 늘려도 US 경합이 줄어든다는 보장은 없고, 보관 Undo가 늘어나는 만큼 공간이 필요함
RETENTION GUARANTEE는 커밋된 Undo 재사용을 막아 공간이 부족하면 DML이 실패할 수 있음

#필수 명령어 또는 SQL

#Oracle 락의 종류와 TM 락 모드

락 작동 위치 목적
래치 SGA 내부 자료구조 공유 메모리 구조를 아주 가볍고 빠르게 보호
버퍼 락 버퍼 캐시 블록 블록 접근을 순차로 처리해 일관성 유지
DML 테이블 락(TM) 테이블 DML 중 테이블 구조 변경(DROP, ALTER) 방지
DML 로우 락(TX) 행 두 트랜잭션이 같은 행을 동시에 바꾸는 것 방지
라이브러리 캐시 락과 핀 Shared Pool의 Library Cache 공유 중인 커서와 PL/SQL 코드가 실행 중 바뀌거나 지워지지 않게 보호
DDL 락 데이터 딕셔너리 딕셔너리 객체 보호(Exclusive DDL, Share DDL, Breakable Parse Lock)
SQL*Plus (실습 계정)객체 또는 설정을 변경함
-- RS
LOCK TABLE emp IN ROW SHARE MODE;
-- RX
LOCK TABLE emp IN ROW EXCLUSIVE MODE;
-- S
LOCK TABLE emp IN SHARE MODE;
-- SRX
LOCK TABLE emp IN SHARE ROW EXCLUSIVE MODE;
-- X
LOCK TABLE emp IN EXCLUSIVE MODE;

결과 해석:

일반 DML(INSERT, UPDATE, DELETE, MERGE)은 RX 모드 TM 락을 잡음
SELECT ... FOR UPDATE는 10g까지 RS, 11g부터 RX로 바뀌었지만 RS와 RX는 서로 호환되므로 동시성 차이는 크지 않다고 수업 자료는 정리함

TM 락은 테이블 전체를 잠그는 것이 아니라 "이 테이블에서 누가 작업 중이니 구조(DDL)를 바꾸지 마라"는 깃발 역할이라, 다른 행의 DML은 막지 않음
SQL Server의 인텐트 락(Intent Lock)과 같은 개념임

#락 대기 전략과 데드락, 커밋 관리

방식 문법 동작
무작정 기다림 SELECT ... FOR UPDATE 상대가 락을 풀 때까지 대기
일정 시간만 기다림 SELECT ... FOR UPDATE WAIT 3 3초 안에 안 풀리면 포기
기다리지 않음 SELECT ... FOR UPDATE NOWAIT 락이 있으면 즉시 ORA-00054

블로킹: 한 트랜잭션이 락을 쥐고 있어 뒤 세션들이 줄줄이 대기하는 상태이며, 앞 세션이 COMMIT이나 ROLLBACK해야 풀림

데드락: 두 트랜잭션이 서로 상대가 가진 행을 기다리는 상태
Oracle이 스스로 탐지해 한쪽 문장을 ORA-00060으로 취소(그 문장만 롤백)하지만, 여전히 락을 쥔 블로킹 상태라 프로그램이 오류를 받고 COMMIT이나 ROLLBACK을 정해야 끝남

트랜잭션을 너무 길게 끌면 락을 오래 잡고 UNDO가 고갈될 수 있고, 너무 자주 커밋하면 커밋마다 LGWR가 로그 버퍼를 디스크로 내려 써서 log file sync 대기가 늘어남
적절한 단위로 묶어 커밋함

SQL*Plus (실습 계정)객체 또는 설정을 변경함
-- 기본값, LGWR 기록 완료까지 대기(동기)
COMMIT WRITE WAIT;
-- 기록을 지시만 하고 바로 진행(비동기)
COMMIT WRITE NOWAIT;
-- 기본값, 커밋마다 즉시 기록
COMMIT WRITE IMMEDIATE;
-- 커밋 요청을 모았다가 한꺼번에 기록
COMMIT WRITE BATCH;

결과 해석:

결제처럼 안전이 최우선인 작업은 기본값 WAIT + IMMEDIATE를 씀

수백만 건을 빠르게 넣는 대규모 배치처럼 속도가 더 중요하면 NOWAIT + BATCH로 커밋 대기를 줄일 수 있지만, 커밋 직후 인스턴스 장애가 나면 방금 커밋한 데이터가 유실될 작은 위험이 있음

#TM 락 확인

SQL*Plus (1번 노드, 실습 계정)객체 또는 설정을 변경함
CREATE TABLE emp_tm AS SELECT * FROM emp;
-- 커밋하지 않음
INSERT INTO emp_tm (empno, ename, sal) VALUES (9999, 'TMTEST', 1000);
SQL*Plus (2번 노드, 실습 계정)데이터 또는 파일을 삭제함, 되돌릴 수 없음
-- 결과: 즉시 ORA-00054
DROP TABLE emp_tm;
ALTER SESSION SET ddl_lock_timeout = 10;
-- 결과: 10초 기다린 뒤 ORA-00054
DROP TABLE emp_tm;
SQL*Plus (SYSDBA)조회만 함
SELECT l.inst_id, l.sid, l.type, o.object_name, l.lmode, l.request
  FROM gv$lock l
  JOIN dba_objects o ON o.object_id = l.id1
 WHERE l.type = 'TM'
   AND o.owner = '<SCHEMA>';

결과 해석: ID1은 잠긴 테이블의 오브젝트 번호임
실습 결과는 EMP_TM, LMODE 3(DML의 Row Exclusive)
DROP 시도 자체는 대기 이벤트가 아니라 즉시 오류이므로 락 정보로 확인함
실습에서는 실습 뒤 1번 노드에서 ROLLBACK했음

배타 테이블 락 재현: 1번 노드가 LOCK TABLE emp_tm IN EXCLUSIVE MODE(모드 6)를 잡고 2번 노드가 UPDATE하면 2번 노드가 enq: TM - contention으로 기다림
GV$LOCK에서 1번 LMODE 6, 2번 REQUEST 3
LOCK TABLE은 트랜잭션이 끝날 때 풀림

#외래키 인덱스 유무 비교

SQL*Plus (1번 노드, 실습 계정)객체 또는 설정을 변경함
CREATE TABLE dept_tm AS SELECT * FROM dept;
ALTER TABLE dept_tm ADD CONSTRAINT dept_tm_pk PRIMARY KEY (deptno);
ALTER TABLE emp_tm ADD CONSTRAINT emp_tm_fk FOREIGN KEY (deptno) REFERENCES dept_tm (deptno);
-- 커밋하지 않음
INSERT INTO emp_tm (empno, ename, deptno) VALUES (9998, 'FKTEST', 10);
SQL*Plus (2번 노드, 실습 계정)데이터 또는 파일을 삭제함, 되돌릴 수 없음
-- 결과: 40번과 무관한데도 enq: TM - contention 대기
DELETE FROM dept_tm WHERE deptno = 40;

결과 해석:

양쪽을 롤백하고 자식 외래키 컬럼에 인덱스를 만든 뒤 같은 순서로 하면 2번 노드의 DELETE가 대기 없이 바로 성공했음

SQL*Plus (1번 노드, 실습 계정)객체 또는 설정을 변경함
CREATE INDEX emp_tm_deptno_ix ON emp_tm (deptno);

결과 해석:

연습 문제: 카테고리(부모)와 상품_fk테스트(자식, 2만 건, 외래 키 카테고리번호)에서 야간 배치가 카테고리 일부를 UPDATE하는 동안 주간 상품 INSERT, UPDATE가 대기한다는 민원이 들어옴
현재 락 상태를 조회해 원인을 진단하고, 자식 외래 키 컬럼에 인덱스를 만들어 해결함
외래 키 컬럼에 인덱스가 없으면 부모 UPDATE, DELETE 때 자식 테이블 전체 범위로 TM 락 경합이 퍼질 수 있음

#HW 경합 재현과 완화

SQL*Plus (SYSDBA)객체 또는 설정을 변경함
-- ASSM
CREATE TABLESPACE ts77 DATAFILE '+data' SIZE 200M SEGMENT SPACE MANAGEMENT AUTO;
-- MSSM
CREATE TABLESPACE ts78 DATAFILE '+data' SIZE 200M SEGMENT SPACE MANAGEMENT MANUAL;
SQL*Plus (SYSDBA)조회만 함
SELECT tablespace_name, segment_space_management
  FROM dba_tablespaces
 WHERE tablespace_name IN ('TS77', 'TS78');

결과 해석:

실습에서는 MSSM 테이블에 두 노드 다섯 세션에서 동시에 INSERT ... SELECT * FROM emp를 1만 번 반복하며 비Idle 대기(enq: HW - contention, gc 계열, buffer busy 등)를 조회하고, 앞뒤로 AWR 스냅샷을 찍어 ADDM 보고서를 만들었음(ADDM과 진단 도구)
이어서 ASSM 테이블에 익스텐트를 미리 확보하고 같은 부하를 주어 비교했음

SQL*Plus (SYSDBA)객체 또는 설정을 변경함
ALTER TABLE <SCHEMA>.emp77 ALLOCATE EXTENT (SIZE 100M);
SQL*Plus (SYSDBA)조회만 함
SELECT inst_id, total_waits, ROUND(time_waited_micro / 1000000, 1) AS wait_sec
  FROM gv$system_event
 WHERE event = 'enq: HW - contention';

결과 해석: 두 측정 사이의 누적값 증가분을 비교함
비교 수치와 Elapsed 결과는 기재 없음
실습 완화 방법에는 크고 균일한 익스텐트 구성과 세그먼트 분할, 노드별 다른 파티션 적재도 있음
실습에서는 실습 뒤 VM 공간 확보를 위해 두 테이블스페이스를 INCLUDING CONTENTS AND DATAFILES로 지웠음

#Undo 점검과 설정

SQL*Plus (SYSDBA)조회만 함
SELECT inst_id, value FROM gv$parameter WHERE name = 'undo_tablespace';

SELECT tablespace_name, COUNT(*)
  FROM dba_rollback_segs
 WHERE status = 'ONLINE'
 GROUP BY tablespace_name;

SELECT inst_id, MAX(maxquerylen) AS max_query_sec, MAX(tuned_undoretention) AS tuned_retention
  FROM gv$undostat
 GROUP BY inst_id;
SQL*Plus (SYSDBA)객체 또는 설정을 변경함
ALTER TABLESPACE undotbs1 ADD DATAFILE '+data' SIZE 20M;
ALTER TABLESPACE undotbs2 ADD DATAFILE '+data' SIZE 30M;
ALTER SYSTEM SET undo_retention = 3600 SCOPE = BOTH SID = '*';

결과 해석:

실습에서는 최장 쿼리가 1시간이라는 가정으로 3600을 설정했고, RETENTION GUARANTEE를 걸어 DBA_TABLESPACES.RETENTION으로 확인한 뒤 실습 VM의 Undo가 작아 NOGUARANTEE로 되돌렸음
공유 스토리지에서는 명령을 실행한 노드와 상관없이 대상 Undo 테이블스페이스가 어느 인스턴스용인지 확인함

#확인 문제: 야간 로그 적재를 두 노드로 나눴더니 더 느려짐

상황: 접속 로그 적재 배치를 두 노드에서 동시에 돌렸더니 오히려 느려졌고, 로그 테이블은 오래전에 만든 테이블스페이스에 있음

SQL*Plus (SYSDBA, 객체 준비 한 번)객체 또는 설정을 변경함
CREATE TABLESPACE ts_oldlog DATAFILE '+DATA' SIZE 300M SEGMENT SPACE MANAGEMENT MANUAL;
CREATE TABLE <SCHEMA>.t_access_log TABLESPACE ts_oldlog AS SELECT * FROM <SCHEMA>.emp WHERE 1 = 2;

결과 해석:

준비 블록은 한 세션에서 한 번만 실행하고, 아래 적재 블록만 두 노드의 네 세션에서 동시에 실행함

SQL*Plus (실습 계정, 두 노드 각 2세션 동시 적재)객체 또는 설정을 변경함
SET TIMING ON
BEGIN
  FOR i IN 1..10000 LOOP
    INSERT INTO <SCHEMA>.t_access_log SELECT * FROM <SCHEMA>.emp;
  END LOOP;
  COMMIT;
END;
/
SQL*Plus (SYSDBA, 1번 노드)조회만 함
SELECT inst_id, event, COUNT(*)
  FROM gv$session
 WHERE state = 'WAITING' AND username = '<SCHEMA>'
   AND wait_class <> 'Idle'
 GROUP BY inst_id, event;

SELECT t.tablespace_name, t.segment_space_management
  FROM dba_tables s
  JOIN dba_tablespaces t ON t.tablespace_name = s.tablespace_name
 WHERE s.table_name = 'T_ACCESS_LOG';
SQL*Plus (SYSDBA, 1번 노드)객체 또는 설정을 변경함
-- 오늘 밤 바로 할 수 있는 조치: 공간을 미리 확보
ALTER TABLE <SCHEMA>.t_access_log ALLOCATE EXTENT (SIZE 100M);

-- 점검 시간의 구조 개선: ASSM 테이블스페이스로 이동
CREATE TABLESPACE ts_newlog DATAFILE '+DATA' SIZE 300M SEGMENT SPACE MANAGEMENT AUTO;
ALTER TABLE <SCHEMA>.t_access_log MOVE TABLESPACE ts_newlog;

결과 해석:

원인과 조치 조건과 영향
동시 적재 경합 두 노드가 같은 세그먼트를 동시에 늘리려 해 수동 공간 관리(MANUAL)에서 enq: HW - contention이 늘어남
노드를 늘린다고 처리 속도가 비례해 늘지 않음
공간 미리 확보 ALLOCATE EXTENT, 당일 바로 적용하는 조치
공간 관리 변경 ASSM 테이블스페이스로 옮기는 구조 개선
테이블 이동 영향 MOVE는 테이블에 락을 걸고 인덱스를 UNUSABLE로 만들므로 인덱스가 있으면 REBUILD까지 포함해 점검 시간에 함

예상값: 대기 이벤트 enq: HW - contention, 공간 관리 방식 MANUAL

조치 뒤 같은 적재를 다시 돌려 Elapsed를 비교하고, 실습이 끝나면 두 테이블스페이스를 INCLUDING CONTENTS AND DATAFILES로 지움

실행 결과 확인 불가

#확인 문제: 업무 중 결제 테이블 컬럼 추가가 ORA-00054로 실패

상황: 결제 테이블에 컬럼을 추가하려는데 ORA-00054로 계속 실패하고, 업무 시간이라 서비스를 내릴 수 없음
실습에서는 2번 노드에서 t_payment에 한 행을 넣고 커밋하지 않은 채 1번 노드에서 ALTER TABLE ... ADD를 실행해 재현함

SQL*Plus (SYSDBA, 1번 노드)조회만 함
SELECT l.inst_id, l.sid, s.serial#, s.username, s.prev_sql_id, l.lmode
  FROM gv$lock l
  JOIN gv$session s ON s.inst_id = l.inst_id AND s.sid = l.sid
  JOIN dba_objects o ON o.object_id = l.id1
 WHERE l.type = 'TM'
   AND o.object_name = 'T_PAYMENT';
SQL*Plus (1번 노드, DDL 실행 세션)객체 또는 설정을 변경함
ALTER SESSION SET ddl_lock_timeout = 60;
ALTER TABLE <SCHEMA>.t_payment ADD (pay_method VARCHAR2(10));
-- 2번 노드 거래가 COMMIT하는 순간 이 ALTER가 완료됨

결과 해석:

예상값: Holder는 INST_ID 2, LMODE 3(미커밋 DML의 TM 락)

DDL은 기다리지 않고 바로 실패하므로 대기 이벤트가 아니라 락 정보(GV$LOCK의 TM)로 Holder를 찾음

DDL이 기다리는 동안 그 뒤에 온 DML도 기다릴 수 있으므로 부하가 낮은 시간에 짧은 대기 시간으로 함

실행 결과 확인 불가

SEO_HKabsumveritas@gmail.comBuilt with Claude Code & Codex

마지막 갱신 개인 학습 기록이며 운영 환경 적용 전 공식 문서 확인 필요

두 글자 이상 입력, 위아래 화살표로 이동하고 Enter로 열기