SQL) 트랜잭션과 Lock Ⅲ

jinsung·2025년 12월 6일

SQL

목록 보기
15/46
post-thumbnail

5. 오라클 Lock

오라클은 공유 리소스와 사용자 데이터를 보호할 목적으로 DML Lock, DDL Lock, 래치, 버퍼 Lock, 라이브러리 캐시 Lock/Pin 등 다양한 종류의 Lock 을 사용한다.
이 외에도 내부적으로 많은 Lock이 존재한다.

애플리케이션 개발 측면에서 가장 중요하게 다루어야 할 Lock은 무엇보다 DML Lock이다. DML Lock은, 다중 사용자에게 의해 동시에 액세스되는 사용자 데이터의 무결성을 보호해 준다.
DML Lock에는 테이블 Lock과 로우 Lock이 있다.

  • DML 테이블 Lock : Enqueue Lock으로 구현
  • DML 로우 Lock : 로우 단위 Lock + TX Lock을 조합해서 구현
    (트랜잭션 Lock 은 Enqueue Lock으로 구현)

1. Enqueue Lock

Enqueue는 공유 리소스에 대해 액세스를 관리하는 Lock 매커니즘이다.
Enqueue에 의해 보호되는 공유 리소스로는 테이블, 트랜잭션, 테이블스페이스, 시퀀스, Temp 세그먼트 같은 것들이 있다.
Enqueue Lock은 래치와 달리 순서가 보장되는 큐 구조를 사용한다.
따라서, 대기자 큐에 가장 먼저 Lock 요청을 등록한 세션이 가장 먼저 Lock을 획득한다.
Enqueue Lock을 획득하는 순서가 있다.

  1. "Enqueue 리소스"를 할당받는다.

    Enqueue 리소스란?

    소유자, 대기자 목록을 관리할 수 있는 구조체를 말함

  2. 각 Enqueue 리소스에는 고유한 식별자가 부여되며, 식별자는 <Type-ID1-ID2>로 구성된다.

    <Type-ID1-ID2> 구조

    Type : TX, TM, TS 처럼 2개의 문자열로 이루어짐
    ID1, ID2 : Lock 종류에 따라 다른 정보를 갖는다

    TM LOCK 식별자TX LOCK 식별자
    TypeTMTX
    ID1오브젝트 IDUndo 세그먼트 번호 + 트랜잭션 슬롯번호
    ID20트랜잭션 슬롯 Sequence 번호
  3. 오라클은 Enqueue 리소스 구조체를 통합 관리하는 리소스 테이블을 갖고 있고, 리소스 테이블에서 관리되는 각 리소스를 찾을 때는 해싱 알고리즘을 사용한다.
  • 해싱 키 : 리소스 식별자

  • 각 해시 버킷 : 연결리스트로 연결된 해시 체인을 가짐, 여기에 리소스 구조체 연결

    여기서도 Enqueue 방식으로 관리되는 특정 리소스에 대해 Lock을 획득하려면, 먼저 리소스 테이블에서 할당 리소스 구조체를 찾는다. 찾지 못하면, 새로운 리소스 구조체를 할당 받아 해시 체인 연결리스트에 직접 연결한다. 그런 후, 리소스 구조체의 소유자 목록에 자신을 등록한다. 호환되지 않는 모드는 먼저 Lock을 획득한 세션이 있다면 Lock 요청을 대기자 목록에 등록하고 대기한다.

    소유자가 Exclusive 모드일 때는 하나의 세션만 Lock 획득 가능
    Shared 모드일 때는 여러 세션이 동시에 Lock 획득 가능


2. TX Lock(= 트랜잭션 Lock)

오라클은 트랜잭션 슬롯에 기록된 상태 정보를 확인하고, 필요하다면 CR 블록을 생성해서 읽어, 레코드가 갱신 중이더라도 읽기 작업에 대해서는 블로킹 없이 작업을 진행할 수 있도록 구현하였다. -> Consistent 읽기, Current 갱신

하지만, 변경 중인 레코드를 동시에 변경하려는 트랜잭션에 대해서는 액세스를 직렬화해야 하며, 그 목적으로 사용하는 Lock 매커니즘이 TX Lock 이다.

TX Lock은 트랜잭션이 첫 번째 변경을 시작할 때 얻고, 커밋 또는 롤백할 때 해제한다.
TX Lock도 Enqueue Lock으로 구현되었다.

v$lock을 통해 TX Lock을 조회할 수 있다.
v$lock를 통해 TX Lock 경합 상황을 모니터링 할 수는 있지만, 발생 원인까지 알 수는 없다. 원인은 v$session_wait 또는 이벤트 트레이스를 통해 대기 이벤트 발생 현황을 관찰해야 한다.
v$session_wait 뷰를 조회해보면 event명, p1, p2, p3 파라미터 등이 나온다.

  • p1 파라미터 : Lock 모드 확인
    이벤트 명이 enq:TX - row lock contention 일 때는 p1 파라미터 값을 보고 Lock 모드를 확인해 발생원인을 판단한다.

  • p2, p3 파라미터 : Undo 세그먼트, 트랜잭션 슬롯 번호, Wrap 시퀀스 번호 식별


TX Lock의 발생 원인을 알아보자

로우 Lock 경합은 UPDATE나 DELETE 시에만 발생한다.
INSERT는 새로운 레코드를 삽입하는 것이므로 로우 Lock 경합이 발생하지 않는다.
하지만, 테이블에 Unique 인덱스가 정의되어 있을 때는 INSERT에 의한 로우 Lock 경합이 발생할 수 있다. 두 개 이상의 트랜잭션이 같은 값을 입력할 때, 선행 트랜잭션이 아직 진행 중이라면 값의 중복 여부가 확정되지 않았으므로 후행 트랜잭션은 진행을 멈추고 대기해야한 하는 것이다.
이 때 enq: TX - row lock contention 대기 이벤트가 Shared 모드로 발생한다. 발생 원인은 무결성 제약 위배 가능성이다.
비트맵 인덱스 엔트리에 대한 갱신을 수행할 때도 Shared 모드로 enq: TX - row lock contention 이 발생할 수 있다.

대기 이벤트 명Lock 모드발생 원인
enq: TX - row lock contentionExclusive(6)DML 로우 Lock
Shared(4)무결성 제약 위배 가능성
Shared(4)비트맵 인덱스 엔트리 갱신
enq: TX - allocate ITL entryShared(4)ITL 부족
enq: TX - index contentionShared(4)인덱스 분할
enq: TX - contentionShared(4)읽기 전용 테이블 스페이스, PREPARED TxN(2PC), Free List 등등

DML Lock은, 다중 사용자에 의헤 동시에 액세스되는 사용자 데이터의 무결성을 보호해 준다. DML 수행 중에 호환되지 않는 다른 DML 또는 DDL 오퍼레이션의 수행을 방지 시켜 주는 것이다.
그 중 로우 Lock은, 두 개의 동시 트랜잭션이 같은 로우를 변경하는 것을 방지한다. 하나의 로우를 변경하려면 로우 Lock을 먼저 획득해야 한다. 오라클은 로우 Lock을 로우 단위 Lock + TX Lock을 조합해서 구현했다.
즉, 로우를 변경하려면 Undo 세그먼트에서 트랜잭션 슬롯을 먼저 할당하고, Enqueue 리소스를 통해 TX Lock을 획득한다.
TX Lock은 트랜잭션을 시작할 때 한 번만 획득한다.


DML 로우 Lock 정리

  • 로우 단위 Lock : 블록 헤더 ITL과 로우 헤더 Lock Byte 설정을 의미하고, 이를 통해 로우를 갱신 중인 트랜잭션 상태를 확인하고 액세스 가능 여부를 결정한다

  • TX Lock : Enqueue 리소스를 통해 TX Lock을 설정하는 것을 의미하고, Lock이 설정된 레코드를 갱신하고자 할 때 Enqueue 리소스에서 대기한다


3. TM Lock

DML 테이블 Lock은, DML 문장 수행 시 자동으로 TM Lock까지 함께 획득하는 매커니즘을 말한다.

오라클은 로우 Lock 획득 시, 해당 테이블에 대한 테이블 Lock도 동시에 획득한다. 그럼으로써 현재 트랜잭션이 갱신 중인 테이블에 대한 호환되지 않는 DDL 오퍼레이션을 방지해 테이블 구조 변경을 막는다.

오라클은 변경 작업에만 로우 Lock을 사용하므로, 로우 Lock은 항상 Exclusive 모드이다. 하지만, TM Lock에는 여러가지 Lock 모드가 사용된다.

NULLRSRXSSRXX
NULLOOOOOO
RS (Row Share)OOOOO
RX (Row Exclusive)OOO
S (Share)OOO
SRX (Share Row Exclusive)OO
XO

선행 트랜잭션과 호환되지 않는 모드로 TM Lock을 설정하려는 후행 트랜잭션은 대기하거나 작업을 포기해야 한다.
INSERT, UPDATE, DELETE, MERGE문을 로우 Lock을 설정하려면 해당 테이블에 RX(=SX) 모드 테이블 Lock을 먼저 획득해야 한다.
SELECT FOR UPDATE 문을 위해 로우 Lock을 설정하려면 RS(=SS) 모드 TM Lock을 먼저 획득해야 한다.
RS, RX 간에는 호환이 되므로 SELECT FOR UPDATE나 DML문 수행 시 이들 간에 TM Lock에 의한 경합은 절대 발생하지 않는다. 다만, 같은 로우를 갱신할 때는 로우 Lock에 의한 경합이 발생한다.

오라클 TM Lock도 Enqueue로 구현돼었으며, 'TM Enqueue' 라고도 부른다.

DML문을 수행할 때 묵시적으로 TM Lock을 얻게 되는데 여기서 세 가지 방법으로 나뉜다.

  • Lock이 해제될 때 까지 기다림
select * from t for update;
  • Lock이 해제될 때 까지 일정 시간만 기다림, 일정 시간 후에 포기
select * from t for update wait 5;
  • Lock을 기다리지 않고 포기
select * from t for update nowait;

TX Lock은 트랜잭션마다 오직 한 개씩 획득하는 반면, TM Lock은 트랜잭션에 의해 변경이 가해진 오브젝트 수 만큼 획득한다.


4. Lock을 푸는 열쇠, 커밋

블로킹과 교착상태의 차이가 대체 뭘까?

블로킹이란 우리가 흔히 알고 있는 Lock 경합이 발생해 특정 세션이 작업을 진행하지 못하고 멈춰 선 경우를 말하고, 이것을 해결하는 방법은 커밋 혹은 롤백 뿐이다.

교착상태는 두 세션이 각각 Lock을 설정한 리소스를, 서로 액세스하려고 마주보고 진행하는 상황을 말하며, 둘 중 하나가 뒤로 물러나지 않으면 영영 풀 수 없다.

오라클에서 교착상태가 나타나면?

  1. 두 세션 중 먼저 인지한 세션이 문장 수준 롤백을 진행한 후에 에러 메시지를 던진다.
    -> 둘 중 하나의 세션을 롤백하라는 것

  2. 이제 교착상태는 해소됐지만 블로킹 상태에 놓이게 된다.
    -> 메시지를 받은 세션이 커밋 또는 롤백을 진행해 에러에 대한 예외 처리해 대기 상태를 풀어줘야 한다.


지금까지의 오라클 Lock 메커니즘 정리

오라클은 데이터를 읽을 때 Lock을 사용하지 않으므로 Lock 경합이 적게 발생한다.
읽는 세션의 진행을 막는 부담감이 없으므로 필요한 만큼 트랜잭션을 충분히 길게 가져갈 수 있다.

  • 그렇더라도 불필요하게 트랜잭션을 길게 정의하지 않도록 주의
  • 같은 데이터를 갱신하는 트랜잭션이 동시에 수행되지 않도록 설계
  • DML Lock 때문에 동시성이 저하되지 않도록 적절한 시점에 커밋

불필요하게 커밋을 자주 수행하면 Snapshot too old 에러를 유발할 가능성이 커지고, LGWR가 로그 버퍼를 비우는 동안 발생하는 log file sync 대기 이벤트 때문에 성능이 저하될 수 있다.
잦은 커밋 때문에 성능이 느리다면, 오라클 10gR2부터는 비동기석 커밋으로 처리가능하다.

4가지 비동기식 커밋 방법

1. WAIT(Defualt)

LGWR가 로그버퍼를 파일에 기록했다는 완료 메시지를 받을 때까지 대기하며, 그 동안 log file sync 대기 이벤트가 발생한다.

2. NOWAIT

LGWR의 완료 메시지를 기다리지 않고 바로 다음 트랜잭션을 진행하므로, log file sync 대기 이벤트가 발생하지 않는다.

3. IMMEDIATE(Defualt)

커밋 명령을 받을 때마다 LGWR가 로그 버퍼를 파일에 기록한다.

4. BATCH

세션 내부에 트랜잭션 데이터를 일정량 버퍼링했다가 일괄 처리한다.

지금까지 사용해 오던 커밋은, 트랜잭션 데이터가 데이터베이스에 안전하게 저장됨을 보장한다.
하지만 비동기식 커밋 옵션을 사용하면, 트랜잭션 커밋 직후 인스턴스에 문제가 생기거나, Redo 로그가 위치한 파일 시스템에 문제가 생겨 쓰기 작업을 진행할 수 없게 되면 커밋이 정상적으로 완료되지 못할 수도 있다. 트랜잭션에 의해 생성되는 데이터 중요도에 따라 이 기능의 활용 여부를 결정해야 한다.

profile
Data Engineer

0개의 댓글