5. 오라클 Lock

조회 수 9139 추천 수 0 2010.06.16 22:17:00
휘휘 *.168.123.1

오라클의 Lock


오라클은 공유리소스와 사용자 데이터를 보호할 목적으로 여러가지 장치를 준비하고 있다.

그 중, 동시 접근하여 정합성을 망가뜨릴 수 있는 위험으로부터 보호해주는 Lock이라것이 있다. (Java의 syncronized와도 같다.)

Lock은 어떤 부분을 동시에 접근못하고 동시간에 오직 한 녀석만이 사용하게 하는 것을 보장하는 개념이다.

단지, 그 영역의 범위를 어디까지 하나로, 여러가지 개념들이 태어나게 되는 것이다. 


이 장에서 볼것은 DML Lock에 대해 알아보려 한다.

DML Lock 지금까지 수차 이야기한 데이터의 무결성을 보호해주기 위한 장치이다. 

다음과 같은 내용을 알아보겠다.

  1. Enqueue Lock
  2. TX Lock
  3. TM Lock
  4. 커밋

DML Lock을 이해하기 위해, 1번부터 살펴보겠다..


(1) Enquenue Lock


이름에서도 알 수 있듯, Enqueue Lock은 큐구조를 사용하여 요청을 적재한다.

Enqueue에 의해 보호되는 리소스는 다음과 같다. (v$resource에서 현재 할당된 리소스를 확인할 수 있다.)

  • 테이블
  • 트랜잭션
  • 테이블스페이스
  • 시퀀스
  • 임시세그먼트
  • 등등...

Enqueue리소스는 소유자, 대기자 목록을 관리할 수 있는 구조체로 되어 있다.

식별자는 <TYPE-ID1-ID2>, 그 내용은 다음과 같다.


TM Lock의 식별자

TM-오브젝트ID-0


TX Lock의 식별자

TX-Undo세그번트번호+트랜잭션슬롯번호-트랜잭션슬롯Sequence번호


Enqueue 리소스구조체는 리소스 테이블에 통합관리 되며, 리소스를 찾을 때는 해싱알고리즘을 사용한다.

아래 그림은 리소스 구조체를 표현한 것이다.

02-129.jpg


각 리소스의 고유식별는 소유자 목록대기자 목록을 갖는다.

소유자 목록에 소유자가 존재하면, 리소스를 얻지못하고 대기자 목록에 등록한다.

소유자 목록의 소유자가 작업이 끝나 리소스의 소유권을 대기자목록 1순위에게 넘기면, 비로소 소유자목록에 등록되어 소유하게 된다.

단, 리소스를 점유하려는 세션에는  Shared모드Exclusive모드가 있어, Shared모드일경우는 공유가 가능하다.

이를테면, 현재 소유자가 Shared모드이고 나도 Shared모드라면 소유자목록에 자신을 등록, 해당 리소스의 Lock을 얻을 수 있다.

그러나, 현재소유자 혹은 내가 Exclusive모드라면 공유하지 못한다..

다음의 경우들을 살펴 보자.


대상세션 : Shared모드


  1. 테이블T의 락을 얻기 위해, 리소스 테이블을 뒤지기 시작.
  2. 테이블T의 고유식별자 발견. 
  3. 소유자 목록 체크 → 비어있다
  4. 자신을 소유자 목록에 등록. 해당 리소스의 Lock을 취득.
  1. 이블T의 락을 얻기 위해, 리소스 테이블을 뒤지기 시작.
  2. 테이블T의 고유식별자 발견. 
  3. 소유자 목록 체크 → 누군가 있다 Shared모드의 세션이 소유중
  4. 자신을 소유자 목록에 등록. 해당 리소스의 Lock을 취득.
  1. 테이블T의 락을 얻기 위해 리소스 테이블을 뒤지기 시작.
  2. 테이블T의 고유식별자 발견. 
  3. 소유자 목록 체크 → 누군가 있다 Exclusive모드의 세션이 소유중
  4. 자신을 대기자 목록에 등록. 대기한다.




대상세션 : Exclusive모드


  1. 테이블T의 락을 얻기 위해, 리소스 테이블을 뒤지기 시작.
  2. 테이블T의 고유식별자 발견. 
  3. 소유자 목록 체크 → 비어있다
  4. 자신을 소유자 목록에 등록. 해당 리소스의 Lock을 취득.
  1. 이블T의 락을 얻기 위해, 리소스 테이블을 뒤지기 시작.
  2. 테이블T의 고유식별자 발견. 
  3. 소유자 목록 체크 → 누군가 있다
  4. 자신을 대기자 목록에 등록. 대기한다.



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


우리는 문장읽기수준 일관성이라는 부분에서 타 트랜잭션이 읽기를 수행하고 있어도 블로킹없이 읽기를 진행할 수 있음을 공부했다.

그러나, 변경 중인 레코드를 동시에 변경하려는 트랜잭션에 대해서는 블로킹작업이 필요하다. (액세스 직렬화)

어떤 트랜잭션이 해당 리소스의 Lock을 얻고자 할때,

해당식별자를 갖는 리소스 구조체를 Enqueue리소스 테이블의 해쉬체인에 연결.

해당 트랜잭션을 소유자목록에 등록함으로써 Lock을 획득하게 된다. (.※식별자와 Enqueue리소스 테이블에 대해서는 윗절 참조)

그리고, 해당 트랜잭션의 일련의 작업이 수행된다

이것이, TX Lock이다.

TX Lock의 메카니즘에 대해 살펴보자.


?2-129.jpg


       TX1                             TX2

  1. Undo세그먼트에서 트랜잭션 슬롯을 할당
    R1~R5에대한 TX Lock설정.(소유자목록에 등록)
  2. R1~R5 Update작업실시                                R6에대한 TX Lock설정
  3. Update완료 (커밋전)                                     R6의 Update작업실시
  4.                                                                 Update완료 (커밋전)
  5.                                                                 R3에대한 TX Lock을 얻기위해 시도.
  6.                                                                 TX1이 소유자에 등록되있다.
  7.                                                                 대기자목록에 등록하고 대기. 
  8.                                                                 3초마다 TX1의 TX Lock상태 감시 (교착상태인경우, 에라가 발생. 롤백됨.)
  9. 커밋 (혹은, 롤백)
  10. 대기자목록에서 우선순위1인 트랜잭션을
    재개하도록 함.
  11.                                                                 R3의 TX Lock설정(소유자목록에 등록)
  12.                                                                 R3의 Update작업실시
  13.                                                                 Update완료
  14.                                                                 커밋.

해당자원에 대해 소유상태와 대기상태를 알아보려면, v$lock뷰를 조회해본다.


SQL>  select sid, type, id1, id2, lmode, request, block
2 , to_char(trunc(id1/power(2,16))) USN
3 , bitand(id1, to_number('ffff', 'xxxx')) + 0 SLOT
4 , id2 SQN
5 from v$lock
6 where TYPE = 'TX' ;

SID TY ID1 ID2 LMODE REQUEST BLOCK USN SLOT SQN
----- -- -------- ----- -------- ----------- ------ --- ------- -------
145 TX 655401 1601 0 6 0 10 41 1601
150 TX 655401 1601 6 0 1 10 41 1601


  • LMODE : 세션모드 (6=exclusive mode)
  • REQUEST : 요청세션모드
  • BLOCK : 1이 소유자, (0 : 대기자)


발생원인을 알아보기 위해서는 v$session_wait뷰를 참조한다.

SQL> select sid, seq#, event, state, seconds_in_wait, p1, p2, p3
2 from v$session_wait
3 where event like 'enq: TX%' ;

SID SEQ# EVENT STATE SECONDS_IN_WAIT P1 P2 P3
--- ---- ---------------------------------- ------- --------------- ---------- ------ -----
158 137 enq: TX - row lock contention WAITING 108 1415053318 589824 7754


 원인

대기이벤트명

 LMODE

 DML 로우 Lock

 enq : TX - row lock contention

 6 (Exclusive)

 무결성 제약 위배 가능성

 enq : TX - row lock contention

 4 (Shared)

 비트맵 인덱스 엔트리 갱신

 enq : TX - row lock contention

 4 (Shared)

 ITL부족

 enq : TX - allocate ITL entry

 4 (Shared)

 인덱스분할

 enq : TX - index contentiony

 4 (Shared)

 읽기전용테이블스페이스,

 Prepared TxN(2PC), Free Lists 등등

 enq : TX - contention

 4 (Shared)


지금부터, 위의 6가지 발생원인에 대해 자세히 알아보기로 하자.


(3) TX Lock >> 무결성 제약 위배 가능성 또는 비트맵 인덱스 엔트리 갱신


로우 Lock경합은 다음과 같은 경우 발생한다.

  1. Update시
  2. Delete시
  3. Insert시 (단, 테이블에 Unique인덱스가 정의된 경우만)

3번의 경우는 동시에 두 데이터가 삽입될경우, 중복인가 아닌가 판단하기 위해서이다.


다음의 예들을 살펴보자.


Dept테이블. deptno에 PK인덱스 설정.

TX1, TX2가 같은 PK의 데이터(deptno = 40)를 삽입.

  1. TX1이 레코드를 삽입. (커밋전)
  2. TX2도 레코드를 삽입.
  3. TX2는 대기. (Shared 모드, enq: TX - row lock contention)
  4. TX1이 커밋한 경우 → TX2는 ORA-00001 에러발생. (무결성 제약 조건 위배)
  5. TX1이 롤백한 경우 → TX2는 정상적으로 삽입 완료.


부서에는 1명이상의 사원이 있을 수 있고, 사원은 1개 부서에만 소속. (Dept테이블, Emp테이블)

부서번호(deptno)로 FK제약 설정.

  1. TX1이 부서 레코드(deptno=40)를 삭제. (커밋전)
  2. TX2가 사원 레코드를 삽입.(deptno=40)
  3. TX2는 대기. (Shared 모드, enq: TX - row lock contention)
  4. TX1이 커밋한 경우 → TX2는 ORA-02291 에러발생. (무결성 제약조건이 위배-부모 키가 없음)
  5. TX1이 롤백한 경우 → TX2는 정상적으로 삽입 완료.


비트맵 인덱스의 엔트리에 대한 갱신시.

사원테이블의 성별(sex)컬럼에 비트맵인덱스가 설정.

※비트맵 인덱스는 하나의 엔트리가 여러개의 레코드와 맵핑되어, 하나가 락이 걸리면 관련된 레코드가 락이 걸리게 됨.

  1. TX1이 사원번호 2010의 레코드를 삽입. (커밋전)
  2. TX2가 사원번호 2009의 레코드를 갱신.
  3. TX2는 대기. (Shared 모드, enq: TX - row lock contention)
  4. TX1이 커밋(혹은 롤백)한 경우 → TX2는 정상적으로 갱신 완료.



(4) TX Lock >> ITL슬롯부족


우리는 트랜잭션이 시작할때, ITL슬롯을 할당받아야 함을 지난강좌들을 통해 배웠다.

ITL슬롯을 할당받아 트랜잭션ID를 기록한 후 비로소 트랜잭션이 시작됨을 의미한다.

빈 ITL슬롯이 존재하지 않는경우 다른 트랜잭션이 끝나기를(커밋 혹은 롤백) 기다려야함도 알고 있다.


이것이 Shared모드 enq:TX-allocate ITL entry 대기 이벤트이다.


ITL슬롯은 다음과 같은 특성을 갖는다.

  1. ITL슬롯당 24Byte 차지.
  2. 블록에 할당하는 ITL슬롯갯수 → INITRANS파라미터로 설정.
    create table emp ( .... ) INITRANS 5 MAXZTRANS 255 PCTFREE 30;
    PCTFREE : Update를 위해 예약된 공간으로 INITRANS로 할당된 공간이 모두 사용중이면 이 공간을 사용한다.
  3. 9i부터 insert시는, 대기없이 새 블록을 찾아 insert를 실시하기에 ITL슬롯경합이 없다. (인덱스의 경우는 제외)
  4. 9i부터 INITRANS의 값은 3이하의 경우 오라클이 강제적으로 3으로 셋팅.
  5. 9i부터 MAXZTRANS의 값은 무시되어 오라클이 강제적으로 255로 셋팅.
    ITL슬롯 경합이 발생하지 않으리라 생각되지만, PCTFREE가 전부 사용되어버리면 거기서 경합발생(Update)


ITL경합이 빈번한 세그먼트에 대해서는 INITRANS를 늘려주는것이 좋다.

경합상태는 다음의 v$segstat(10g부터)에서 확인할 수 있다.

select ts#, obj#, dataobj#, sum(value) itl_waits from v$segstat where statistic_name = 'ITL waits'
group by ts#, obj#, dataobj# having sum(value) > 0
order by sum(value) desc


주의할 점이, INITRANS를 변경하더라도 기존에 할당된 블록에는 반영이 않된다. (새 블록에만 적용)

필요하다면, 기존의 블록에 반영하는 작업을 다음과 같이 해줄수 있다.

alter table t move INITRANS 5;      → 인덱스가 모두 unusable 되므로 주의!
alter index t_idx rebuild INITRANS 5;



(5) TX Lock >> 인덱스 분할


테이블은 정렬상태를 유지할 필요가 없기에 입력공간이 부족하면 다른 곳의 새 블록에 입력하면 된다.

그러나, 인덱스는 정렬상태를 유지해야 하므로 순서를 의식한 끼워넣기가 빈번하게 발생할 수 있다.

이때, 인덱스분할을 행하고, 이 과정에서 Lock경합이 발생한다.

이것이 Shared 모드 enq:TX - index contention 대기이벤트이다.


다음 그림을 보자.

  1. 트랜잭션이 5번위치(블록)에 데이터 삽입을 시도 (현재 5번은 공간이 없다.)
  2. 결국, 5번의 크기를 늘려야 하므로 5번과 6번사이에 블록을 추가 (10번블록)
  3. 5번블록의 데이터절반을 10블록으로 옮긴다. (결과적으로 5번위치가 2배커진 효과다)



위의 그림에서,

  1. 트랜잭션이 9번위치(블록)에 데이터 삽입을 시도 (현재 9번은 공간이 없다.)
  2. 단순히, 9번블록 다음에 블록을 추가 (11번블록)


인덱스분할이 진행되는 동안, 다른 트랜잭션은 해당 인덱스가 걸린 자원에 접근하지 못하고 대기 상태가 된다.
만약, 분할을 실시하던 트랜잭션이 다른 작업으로 인해 인덱스분할이 끝난후에도 종료되지 않는다면, 쓸데없이 Lock의 범위가 넓어지게 되는 현상이 되버린다.
이를 막기위해, 오라클은 인덱스분할 작업에 대해 자율형트랜잭션을 이용하여 작업을 실시한다. (4절 동시성 구현사례 참고)
즉, 인덱스분할이 끝나면 (오라클이 생성한)인덱스분할작업용 트랜잭션은 종료되어 元트랜잭션이 종료되지 않아도 인덱스에 대한 락이 해재된다.


(6) TX Lock >> 기타 트랜잭션 Lock


Shared 모드 enq:TX - contention 대기이벤트.

앞서 설명한 이외의 트랜잭션 대기 상황을 모두 포함하는 듯하다. (저자의 추측)

저자왈.

USERS테이블스테이스에 DML을 수행하는 트랜잭션이 남아있을 때, 읽기전용으로 테이블스페이스를 전환할때도 위의 경합이 발견되었다.

alter tablespace USERS read only;



(7) TX Lock >> DML 로우 Lock


DML Lock은 다중 사용자에 의해 동시 액세스되는 데이터의 무결성을 보해해준다.

그 중 로우Lock은 한 로우에 동시에 접근하여 같은 로우를 변경하는 것을 방지한다.

오라클의 로우 Lock로우 단위 LockTX Lock을 조합하여 구현하고 있다.
DML 로우 Lock에 의한 TX Lock때문에 블로킹된 세션을 관찰해보면, Exclusive 모드 enq:TX - row lock contention 대기이벤트가  지속적으로 나타난다.

이 의미를 정확히 이해할 필요가 있다.

  1. 로우 단위 Lock
    블록헤더 ITL과 로우헤더 Lock Byte 설정을 의미. 이로써, 해당로우에의 악세스 가능여부를 결정.
  2. TX Lock
    Enqueue 리소스를 통해 TX Lock을 설정하는 것을 의미. 이로써, Lock설정된 레코드에 대해 대기를 할 수 있다.


즉, 해당로우가 악세스 불가로, 악세스 가능하기를 기다리려고 해당 Enqueue리소스를 통해 TX Lock을 설정 대기중이고, 이에 의해 또 다른 세션이 블로킹을 당하고 있다면 그 세션은 Exclusive 모드 enq:TX - row lock contention 대기이벤트가  지속적으로 나타난다.

라고 해석할 수 있다.


※로우 단위 Lock에 대해서는 , 1장의 5절 UNOD과 6절 문장읽기수준일관성을 참조
※TX Lock에 대해서는 , 본절의 앞서했던 설명들을 참조



(8) TM Lock >> DML 테이블 Lock


트랜잭션 수행중, 해당 테이블의 구조가 변경된는 것을 막기위해 로우 Lock을 획득함과 동시에 테이블 Lock도 획득한다.

테이블 Lock이라 하여 테이블 전체가 블로킹되어 아무 작업도 할수 없음을 의미하는 것이 아니다.

단지, 일종의 표식으로써 해당테이블에 대해 어떠한 작업중임을 알리려는 목적이다.

거기에는 작업의 모드에 따라 후행 트랜잭션이 자신의 작업을 진행할지 아니면, 기다릴지, 혹은 포기할지를 결정한다.

작업 모드가 선행 트랜잭션과 호환성이 없으면 대기를 하던지 포기를 해야 한다.

작업모드는 다음과 같다.

02-144.jpg

참고적으로, insert, delete, update, merge등은 RX(=SX)모드이고 Select for update문은 RS(=SS)모드이다.


테이블 Lock은 TM Enqueue라 불리는 TM Lock을 설정하는 것으로 획득및 대기가 이루어진다.

원리는 앞절의 TX Lock과 같다.

대기중인 경우는 enq:TM - contention 대기 이벤트가 지속적으로 나타난다.





(9) 락을 푸는 열쇠 커밋


블로킹과 교착상태의 의미적 차이를 이해하자.

  • 블로킹은 앞서 점유된 것이 자유로워질때까지 잠시 대기하는 상태이다. 커밋과 롤백으로 점유된 물건은 자유로워진다.
  • 교착상태는 서로 맞물려 대기하게 되버리는 것이다.
    즉, TX1이 테이블 A를 갱신후 테이블 B를 갱신하려 한다. (A가 블로킹상태)
    때마침, TX2는 테이블 B를 갱신후 테이블 A를 갱신하려고 하고 있다. (B가 블로킹상태)
    결국, TX1은 B의 블로킹이 해제되기를 기다린다. TX2는 A의 블로킹이 해제되기를 기다린다.
    서로가, 각자가 블로킹시킨 자원으로 인해 상대방을 대기시켜버리는 것.
    이것이 교착상태.

교착상태의 경우, 먼저 인지한쪽이 교착상태를 발생시킨 문장 하나만을 롤백한다. 그리고 다음의 에라메세지를 던진다.

※Lock경합에 의해 대기상태에 빠질때, 3초타임아웃을 설정하는 이유가 이것이다.

ORA-00060: deadlock detected while waiting for resource


  • 트랜잭션긴경우 : 롤백에 시간소요, Undo세그먼트 고갈로 Undo세그먼트의 경합이 유발.
  • 빈번한 커밋의 경우 : Snapshot too old(ORA-01555)에러의 유발가능성이 높아져.
                                 LGWR가 로그 버퍼를 비우는 동안 발생하는 log file sync대기이벤트로 인해 성능저하.
    ※ 1장 4절.REDO와 5절 UNDO를 참조.
빈번한 커밋의 경우는 오라클 10gR2부터 제공되 는 비 동기식 커밋기능을 활용할수 있다.
  1. ?WAIT(default) : LGWR가 로그버퍼를 파일에 기록, 완료 메세지를 받을 때까지 대기, log file sync대기 이벤트 발생.(동기커밋)
  2. NOWAIT : LGWR의 완료메세지를 기다리지 않고 바로 다음 트랜잭션을 진행하므로, log file sync대기 이벤트 발생않음.(비동기커밋)
  3. IMMEDIATE(default) : 커밋명령을 받을 때마다 LGWR가 로그버퍼를 파일에 기록.
  4. BATCH : 세션 내부에 트랜잭션 데이터를 일정량 버퍼링한후 일괄 처리.

SQL> COMMIT WRITE IMMEDIATE WAIT;               - 68

SQL> COMMIT WRITE IMMEDIATE NOWAIT;           - 9

SQL> COMMIT WRITE BATCH WAIT;                    - 66       

SQL> COMMIT WRITE BATCH NOWAIT;                - 6


위의 수치는 임의의 루프를 돌려 테스트했을때 상대적 수치다.

Nowait에 대해서는 상당한 개선효과 가 보인다. IMMEDIATE와 BATCH를 비교해 보면 이들 사이의 효과는 미비해보인다.

참고적으로, 비동기식 커밋 옵션을 사용하면, 커밋 직후 인스턴스에 문제가 생기거나 할 때 정상적으로 회복시키기가 어려운 문제점도 있다. (동기식처럼 확실히 Redo/Undo작업이 완료되었으리라는 보장이 없다.)

데이터 중요도에 따라 결정하기를.


<2장 5절 끝>