오라클은 공유 리소스와 사용자 데이터를 보호할 목적으로 다양한 종류의 Lock을 사용합니다.
앞서 래치와 버퍼 Lock에 대해서는 1장에서 다루었습니다.
래치 : SGA에 공유돼 있는 갖가지 자료구조를 보호할 목적으로 사용하는 가벼운 Lock
버퍼 Lock : 버퍼 블록에 대한 액세스를 직렬화
라이브러리 캐시 Lock과 Pin은 라이브러리 캐시에 공유된 오브젝트 정의,커서, PL/SQL 프로그램 같은 실행가능 오브젝트에 대한 정의 및 실행계획을 보호하는 Lock입니다.
라이브러리 캐시 Lock : 라이브러리 캐시 오브젝트에 대한 핸들을 보호
라이브러리 캐시 Pin : 라이브러리 캐시 오브젝트의 실제 내용이 담긴 힙(heap)을 보호
DML Lock은 다중 사용자에 의해 동시에 액세스되는 사용자 데이터의 무결성을 보호해 줍니다. 테이블 Lock과 로우 Lock이 있습니다.
DML 테이블 Lock : Enqueue Lock으로 구현함
DML 로우 Lock : 로우 단위 Lock과 트랜잭션 Lock을 조합해서 구현함(트랜잭션 Lock은 Enqueue Lock으로 구현)
DML Lock을 이해하려면 Enqueue Lock 구조와 트랜잭션 Lock 개념을 먼저 이해해야 합니다. 앞으로 설명할 내용입니다.
(1) Enqueue Lock
Enqueue는 공유 리소스에 대한 액세스를 관리하는 Lock 매커니즘입니다. Enqueue에 의해 보호되는 공유 리소스로는 테이블, 트랜잭션, 테이블스페이스, 시퀀스, Temp 세그먼트 등이 있습니다. Enqueue Lock은 래치와 달리 순서가 보장되는 큐 구조를 사용하고 대기자 큐에 가장 먼저 Lock 요청을 등록한 세션이 가정 먼저 Lock을 획득합니다.
Enqueue Lock으로 관리되는 공유리소스에 대해 Lock을 획득하려면 먼저 ‘Enqueue 리소스’를 할당받아야 합니다. ( v$resource뷰를 통해서 확인할 수 있습니다.) Enqueue 리소스는 소유자, 대기자목록을 관리할 수 있는 구조체를 말합니다. 각 Enqueue 리소스에는 고유한 식별자가 부여되며, 식별자는 <Type-ID1-ID2>로 구성됩니다. Type은 TX,TM,TS처럼 2개 문자열로 이루어지며, ID1, ID2에는 Lock 종류에 따라 다른 정보를 갖습니다.
예를 들어 TM lock식별자에는 다음과 같은 정보를 포함합니다.
TYPE : TM
ID1: 오브젝트 ID
ID2: 0
TX Lock 식별자에는 다음과 같은 정보를 포함합니다.
TYPE: TX
ID1: Undo 세그먼트 번호 + 트랜잭션 슬롯 번호
ID2: 트랜잭션 슬롯 Sequence 번호
오라클은 Enqueue 리소스 구조체를 통합 관리하는 리소스 테이블을 갖고 있으며 리소스 테이블에서 관리되는 각 리소스를 찾을때는 해싱 알고리즘을 사용합니다. 물론 해싱을 위한 해시 키로는 리소스 식별자가 사용됩니다. 각 해시 버킷에는 연결 리스트로 연결된 해시 체인을 가지며, 여기에 리소스 구조체가 연결됩니다.
Enqueue 방식으로 관리되는 특정 리소스(테이블,트랜잭션)에 대해 Lock을 획득하려면,
먼저 리소스 테이블에서 해당 리소스 구조체를 찾습니다.
리소스 구조체를 찾지 못하면, 새로운 리소스 구조체를 할당받아 해시 체인 연결 리스트에 연결합니다.
그 후, 리소스 구조체의 소유자 목록에 자신을 등록하면 됩니다. 호환되지 않는 모드로 먼저 Lock을 획득한(소유자 목록에 등록된) 세션이 있다면 Lock 요청을 대기자 목록에 등록하고 대기해야 합니다.(또는 작업을 포기하는 선택을 할 수 있습니다)
소유자가 Exclusive 모드일 때는 한 순간에 하나의 세션만 Lock을 획득할 수 있지만, Shared 모드일 때는 여러 세션이 동시에 Lock을 획들할 수 있습니다. 즉 여러 세션이 동시에 소유자 목록에 등록될 수 있습니다. 소유자 목록에 Shared 또는 Exclusive 모드 Lock이 등록된 상태에서 Exclusive 모드 Lock을 획득하려는 세션은 대기자 목록에서 대기해야 하며, 하나의 리소스 구조체 대기자 목록에 동시에 여러 세션이 등록된 상태로 대기할 수 있습니다.
Enqueue Lock의 작동 매커니즘은 아래와 같습니다.
1. A세션이 Shared 모드로 Lock을 획득합니다.
2. B세션이 Shared 모드로 Lock을 획득하려고 합니다. 먼저 Lock을 소유한 A세션과 호환되므로 정상적으로 Lock을 획득합니다. 이제 소유자 목록에는 두 개 세션이 달려 있습니다.
3. C세션이 Exclusive 모드 Lock을 획득하려고 합니다. Shared 모드와 Exclusive 모드 간에 호환성이 없으므로 대기자 목록에 자신을 등록하고 대기합니다.
4. 소유자 목록에 Shared 모드로 달려 있던 A,B 세션이 모두 Lock을 해제하면 C세션이 Exclusive 모드로 소유자 목록에 등록됩니다.
5. A세션이 Exclusive 모드로 다시 Lock을 획득하려고 하면, Exclusive 모드와 호환되지 않으므로 대기자 목록에 자신을 등록하고 대기합니다.
6. B세션이 다시 Shared 모드로 Lock을 획득하려고 할때도 Exclusive 모드와 호환되지 않으므로 대기자 목록에 자신을 등록하고 대기합니다.
7. Enqueue Lock은 순서가 보장되므로 C세션이 LocK을 해제하면 A세션이 가장 먼저 Exclusive 모드로 Lock을 획득한다.(그다음은 B세션)
(2) TX Lock (=트랜잭션 Lock)
트랜잭션을 시작하려면 먼저 Undo 세그먼트 헤더에 위치한 트랜잭션 테이블로부터 슬롯을 하나 할당받아야 합니다. 이 트랜잭션이 변경을 가한 블록에 대한 Consistent 버전을 얻으려는 다른 트랜잭션은 트랜잭션 슬롯에 기록된 상태정보를 확인하고 필요하다면 CR 블록을 생성하여 읽습니다. 그렇게 함으로써 오라클은 레코드가 갱신 중이더라도 읽기 작업에 대해서는 블로킹없이 작업을 진행할 수 있도록 구현하였습니다.
하지만 변경 중인 레코드를 동시에 변경하려는 트랜잭션에 대해서는 액세스를 직렬화해야 하며 그 목적으로 사용하는 Lock 매커니즘이 트랜잭션 Lock입니다.
TX Lock은 트랜잭션이 첫 번째 변경을 시작할 때 얻고, 커밋 또는 롤백할 때 해제합니다. Enqueue Lock으로 구현되었습니다.
TYPE: TX
ID1: Undo 세그먼트 번호 + 트랜잭션 슬롯 번호
ID2: 트랜잭션 슬롯 Sequence 번호
이 식별자를 갖는 리소스 구조체를 Enqueue 리소스 테이블 해시 체인에 연결하고, 소유자 목록에 트랜잭션을 등록함으로써 Lock을 획득합니다. 이제 TX lock을 획득하였으므로 트랜잭션을 위한 일련의 작업들을 수행할 수 있습니다.
1. TX1 트랜잭션은 Undo 세그먼트에서 트랜잭션 슬롯을 할당 받고, Enqueue 리소스를 통해 TX lock을 설정합니다. 이 상태에서 r1부터 r5까지 5개의 레코드를 변경하고 아직 커밋은 하지 않았습니다.
2. TX2 트랜잭션도 트랜잭션 테이블에서 하나의 슬롯을 할당 받고, Enqueue 리소스를 통해 TX lock을 설정한 후 r6 레코드를 변경합니다.
3. 이제 TX2가 r3 레코드를 액세스하려는 순간 호환되지 않는 모드로 Lock이 걸려 있음을 인지하고 TX1의 트랜잭션 슬롯 상태를 확인합니다.
4. TX1이 아직 커밋되지 않은 Active 상태이므로 Tx2는 Tx1이 Lock을 설정한 Enqueue 리소스 구조체 대기자 목록에 자신을 등록하고 대기상태로 들어갑니다.
5. TX2는 대기하면서 3초마다 한번씩 TX1이 설정한 TX lock의 상태를 확인합니다. 교착상태(deadlock)발생 여부를 확인하기 위함입니다.
6. TX1이 커밋 또는 롤백하면, TX1이 설정한 TX lock 대기자 목록에서 가장 우선순위가 높은 TX2 트랜잭션을 깨워 트랜잭션을 재개하도록 합니다.
7. Tx2는 r3 레코드를 변경합니다.
v$lock을 통해 TX 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
150번 세션이 145번 세션의 진행을 블로킹하고 있습니다. (block = 1 1은 소유자 0은 대기자)
145번 세션은 150번 세션이 Exclusive 모드로 획득한 TX lock을 Exclusive 모드로 요청한채 대기하고 있습니다.(lmode=6,request=6)
현재 경합이 발생한 tx lock 식별자는 <TX-655401-1601>이며 tx lock을 소유한 트랜잭션의 undo 세그먼트와 트랜잭션 슬롯번호,시퀀스번호까지 식별 할 수 있습니다.
하지만 v$lock은 발생원인 까지 모니터링 할 수 없습니다. 원인을 알기 위해서는 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
관찰된 대기 이벤트 명에 따라 Tx lock을 아래와 같이 구분할 수 있습니다.
이벤트명어 row lock contention일 때는 lock 모드에 따라 그 발생원인을 판단해야 합니다. lock모드는 이벤트 발생시 함께 기록되는 p1파라미터를 통해 활인할 수 있습니다.
(3) TX lock > 무결성 제약 위배 가능성 또는 비트맵 인덱스 엔트리 갱신
row lock contention은 일반적으로 update나 delete 시에만 발생합니다. insert는 새로운 레코드를 삽입하는 것이므로 발생하지 않습니다. 하지만 테이블에 unique 인덱스가 정의되어 있을때는 insert에 의한 row lock contention이 발생할 수 있습니다. 두 개 이상 트랜잭션이 같은 값을 입력하려 할 때, 선행 트랜잭션이 아직 진행 중이라면 값의 중복여부가 확정되지 않았으므로 후행 트랜잭션은 진행을 멈추고 대기해야만 합니다.
dept테이블 deptno 컬럼에 PK인덱스가 생성돼 있는 상태에서 두 트랜잭션이 다음과 같이 진행하면 enq : TX - row lock contention 이벤트가 shared 모드로 발생합니다.
1. 트랜잭션 TX1이 dept 테이블에 deptno=40인 레코드를 입력
2. 트랜잭션 TX2도 dept 테이블에 deptno=40인 레코드를 입력하면, tx1이 커밋 또는 롤백할때 까지 Shared 모드로 enq : TX - row lock contention 이벤트가 발생
3. TX1이 커밋하면 TX2는 ORA-00001에러를 발생
“ ORA-00001 : 무결성 제약조건(PK_DEPT)에 위배됩니다”
4.TX1이 롤백하면 TX2는 정상적으로 입력 완료
dept와 emp테이블이 1:M 관계고, deptno컬럼으로 dept,deptno를 참조하도록 emp 테이블에 FK 제약이 설정돼 있다고 가정하고 두 트랜잭션이 아래와 같이 진행하면 같은 대기 이벤트가 shared 모드로 발생합니다.
1.TX1이 dept테이블에 deptno=40인 레코드를 삭제
2. TX2사 emp 테이블에서 deptno=40인 레코드를 입력하면, TX1이 커밋 또는 롤백할 때까지 shared 모드로 enq : TX - row lock contention 대기 이벤트가 발생
3.TX1이 커밋하면 TX2는 ORA-02291에러를 발생
“ORA-02291:무결정 제약조건(FK_EMP_DEPT)이 위배되었습니다 - 부모 키가 없습니다”
4. TX1이 롤백하면 TX2는 정상적으로 입력이 완료
비트맵 인덱스는 그 구조상 하나의 엔트리가 여러 개 레코드와 매핑됩니다. 하나의 엔트리에 Lock을 설정하면 매핑되는 레코드 전체에 Lock이 설정되므로 비트맵 인덱스 엔트리를 두 개 이상 트랜잭션이 동시에 갱신할 때 이 이벤트가 자주 발생합니다.
TX1 트랜잭션이 1번 레코드를 갱신하는 동안 TX2 트랜잭션이 2번 레코드를 갱신하려고 할 수 있는데, Shared 모드로 enq : TX - row lock contention이 발생합니다.
(4) TX Lock > ITL 슬롯 부족
블록에 레코드를 추가/갱신/삭제하려면 ITL 슬롯을 먼저 할당 받고 그 곳에 트랜잭션 ID를 먼저 기록해야 합니다. ITL 슬롯이 없다면, ITL슬롯을 사용 중인 트랜잭션 중 하나가 커밋 또는 롤백할 때까지 기다려야 하며, 이때 Shared 모드 enq : TX - allocate ITL entry가 발생합니다. 결국 한 블록을 동시에 갱신할 수 있는 트랜잭션 개수는 ITL 슬롯에 의해 결정됩니다. 참고로, ITL 슬롯당 24바이트 공간을 차지합니다.
블록에 기본적으로 할당할 ITL 슬롯 개수는 INITRANS 파라미터로 설정합니다.
create table t(...) initrans 5 maxtrans 255 pctfree 30;
pctfree는 컬럼 update를 위해 예약된 공간입니다. 하지만 INITRANS에 의해 미리 할달된 ITL 슬롯이 모두 사용중일때, 새로운 트랜잭션이 ITL 슬롯을 요청하면 pctfree 설정에 의해 비워둔 공간을 활용하게 됩니다. 이공간까지 활용해 최대한 생성할 수 있는 ITL 슬롯 개수는 maxtrans에 의해 결정됩니다. ITL 슬롯 부족에 의한 대기 현상이 발생했다면 아래 둘중 하나에 해당합니다.
- 동시에 블록을 갱신하려는 트랜잭션 개수가 maxrans값을 초과
- pctfree를 0으로 지정했거나 pctfree 예약 공간을 모두 사용한 상태에서, 새로운 트랜잭션을 위한 ITL 슬롯이 부족
테이블에 insert할 때는 ITL 슬롯이 부족하더라도 굳이 그 블록에 insert하려고 대기할 필요가 없습니다. 새 블록을 할당해 그 곳에 insert하면 되기 때문에, 오라클 9i부터 그렇게 동작하기 시작했습니다. 따라서 insert시 테이블 블록에 대한 ITL 경합이 발생하지 않습니다. 하지만 인덱스에 값을 삽입할 때는 정렬 상태를 유지해야 하므로 여전히 ITL 경합이 방생합니다. update,delete일때는 테이블, 인덱스를 불문하고 ITL 경합이 나타날 수 있습니다.
9i부터는 테이블에 initrans를 3보다 작게 설정하더라도 기본적으로 3개의 ITL 슬롯을 할당합니다.
10g에서는 maxtrrans를 위해 사용자가 지정한 값은 무시되며 255로 항상 고정됩니다.
이 값이 255이라고 해서 ITL 경합을 염려하지 않아도 되는것은 아닙니다. pctfree에 의해 예약된 공간이 update에 의해 모두 사용되고 나면 여전히 ITL 경합이 발생할 수 있기 때문입니다.
ITL 경합에 의한 대기 현상이 자주 발생하는 세그먼트(테이블,인덱스,파티션)에 대해서는 initrans를 늘려주어야 하며, 그런 세그먼트 목록은 v$segstat을 통해 확인할 수 있습니다.
initrans 값을 변경하더라도 기존에 할당된 블록의 ITL 슬롯 개수는 변함이 없고, 새로 할당된 블록에만 적용됩니다. 따라서 기존 블록에서 ITL 경합이 빈번하게 발생한다면 테이블 또는 인덱스 전체를 재생성해 줘야만 합니다.
alter table t move initrans 5; ->인덱스가 모두 unsable 되므로 주의!
alter index t_idx rebuild initrans 5;
(5) TX Lock > 인덱스 분할
테이블은 레코드 간 정렬상태를 유지하지 않기 때문에 입력공간이 부족할 때 새로운 블록을 할당받아 입력하면 됩니다. 인덱스는 정렬된 상태를 유지해야 하므로 아무 블록에나 값을 입력할 수 없습니다. 따라서 값을 입력할 위치에 빈 공간이 없으면 인덱스 분할(split)을 실시해 새 값을 입력할 공간을 확보하게 되며, 이 과정에서 Lock 경합이 발생할 수 있습니다.
인덱스 분할이 진행되는 동안 그 블록에 새로운 값을 입력하려는 또 다른 트랜잭션이 생길 수 있다는 점입니다. 그러면 두 번째 트랜잭션은 선행 트랜잭션이 인덱스 분할을 완료할 때까지 대기해야 하며, Shared 모드에서 enq : TX - index contention 이벤트를 만나게 됩니다.
tx lock은 선행 트랜잭션이 커밋 또는 롤백할 때 비로소 해제되는데, 만약 인덱스 분할을 진행한 트랜잭션이 커밋하지 않은 채 계속 다른 갱신 작업을 진행한다면 Tx lock을 대기하던 트랜잭션은 어떻게 될까요? 계속 대기해야만 하는걸까?
오라클은 autonomous 트랜잭션을 통해 이 문제를 해결합니다.
1. TX1 트랜잭션이 인덱스에 로우를 삽입하려는 순간 빈공간을 찾지 못했다. 인덱스 분할이 필요합니다.
2. TX1 트랜잭션은 autonomous 트랜잭션 TX2를 생성해 인덱스 분할을 진행토록 합니다.
3. 인덱스 분할이 진행 중인 블록에 TX3 트랜잭션이 로우를 삽입하려고 합니다. 이 트랜잭션은 enq : TX - index contention이벤트를 만나게 되고, TX2 트랜잭션이 커밋할때까지 대기합니다.
5.인덱스 분할이 완료되면 TX2 트랜잭션은 커밋합니다. autonomous 트랜잭션이므로 TX1은 커밋되지 않은 상태로 계속 트랜잭션을 진행할 수 있습니다.
6. TX3 트랜잭션도 작업을 재개합니다.
인덱스에서의 PCTFREE 설정은 인덱스를 처음 생성하거나 재생성하는 시점에만 적용되기 때문에, 인덱스 분할을 최소화하는 방만으로 PCTFREE를 증가시키는 것은 일시적이거나 효과가 없다고 할 수 있습니다.
테이블에서의 PCTFREE공간이 나중에 발생할 update를 위해 남겯는 공간인데 반해 인덱스에서의 PCTFREE공간은 insert를 위해 남겨두는 공간입니다.
인덱스에는 update개념이 없기 때문에(delete->insert) 테이블처럼 update를 위해 공간을 남겨둔다면 영영 사용되지 않는 죽은 공간이 될 것입니다.
참고로, 우측 맨 끝으로만 값이 입력되는 Right Growing 인덱스라면, PCTFREE를 0으로 설정하는 것이 인덱스 크기를 줄이는데에 도움이 됩니다. ( 인덱스 분할을 생각하지 않아도 되기 때문에 그에 따른 공간을 0으로 하여 크기를 줄이는 것입니다.)
(6) TX lock > 기타 트랜잭션 Lock
Shared 모드 enq : TX - contention 에 대해 오라클 매뉴얼을 찾아보면 분산 트랜잭션에서 2-Phase 커밋을 위한 Prepared TX Lock을 대기할 때 발생한다고 설명돼 있을 뿐 더 자세한 설명은 얻을 수 없습니다. 추측하면, 앞에서 열거한 중요한 TX lock이외에 트랜잭션 대기 상황을 모두 여기에 포함한다고 보면 됩니다.
읽기 전용 테이블스페이스로 전환할 때도 이 tx lock이 발생합니다. users 테이블스페이스에 DML을 수행하는 트랜잭션이 아직 남아있는 상태에서 아래 명령을 수행하면 Shared Mode로 TX lock을 대기합니다.
alter tablespace USERS read only;
(7) TX Lock > DML 로우 Lock
DML Lock은, 다중 사용자에 의해 동시에 액세스되는 사용자 데이터의 무결성을 보호해 줍니다. DML 수행 중에 호환되지 않는 다른 DML 또는 DDL 오퍼레이션의 수행을 방지시켜 주는 것입니다.
그 중 로우 Lock은 두 개의 동시 트랜잭션이 같은 로우를 변경하는 것을 방지합니다. 하나의 로우를 변경하려면 로우 Lock을 먼저 획득해야 합니다. 오라클은 로우 Lock을, 로우 단위 Lock과 TX Lock을 조합해서 구현하였습니다.
즉 로우를 갱신하려면 Undo 세그먼트에서 트랜잭션 슬롯을 먼저 할당받고, Enqueue 리소스를 통해 TX Lock을 획득합니다.
그 후 insert,delete,update,merge 문장을 통해 갱신하는 각 로우마다 Exclusive 모드로 로우 단위 Lock을 획득합니다. Tx Lock은 트랜잭션을 시작할 때 한번만 획득합니다.
로우 단위 Lock(row level Lock)은 tx1 트랜잭션이 로우 정보를 갱신할 때는, 블록 헤더 ITL 슬롯에 트랜잭션 ID를 기록하고 로우 헤더에 이를 가리키는 Lock byte를 설정합니다. 이 레코드를 액세스하려는 다른 트랜잭션은, 로우 헤더에 설정한 Lock byte를 통해 ITL 슬롯을 찾고, ITL 슬롯이 가리키는 Undo 세그먼트 헤더의 트랜잭션 슬롯에서 트랜잭션 상태 정보를 확인함으로써 해당 레코드에 대한 액세스 가능 여부를 결정합니다.
tx1 이 진행중일때 이 레코드를 읽으려는 tx2는 tx1의 상태를 확인하고 CR블록을 생성해서 읽기 작업을 완료합니다.
TX Lock은 tx1이 갱신 중인 레코드를 같이 갱신하려는 tx2 트랜잭션은 tx1이 완료될 때까지 대기해야합니다.
(8) TM Lock > DML 테이블 Lock
오라클은 로우 Lock 획득 시, 해당 테이블에 대한 테이블 Lock도 동시에 획득합니다. 그럼으로써 현재 트랜잭션이 갱신 중인 테이블에 대한 호환되지 않는 DDL 오퍼레이션을 방지합니다. 테이블 구조를 변경하지 못하도록 막는 것입니다.
테이블 lock을 걸려면 아래처럼 명시적으로 lock table 명령어를 이용할 수도 있습니다.
lock table emp in row share mode
lock table emp in row exclusive mode
lock table emp in share mode
lock table emp in share row exclusive mode
lock table emp in exclusive mode
지금 설명하려는 테이블 lock은 DML문장 수행 시 자동으로 테이블 lock 까지 함께 획득하는 매커니즘을 말합니다.
오라클은 변경 작업에만 로우 lock을 사용하므로, 로우 lock은 항상 exclusive모드입니다. 하지만 테이블 lock에는 여러가지 lock모드가 사용되며, lock모드간 호환성을 정리하면 아래 표와 같습니다.
선행 트랜잭션과 호환되지 않는 모드로 테이블 lock을 설정하려는 후행 트랜잭션은 대기하거나 작업을 포기해야 합니다.
insert,update,delete,merge 문을 위해 로우 lock을 설정하려면 해당 테이블에 RX 모드 테이블 lock을 먼저 획득해야 합니다. select for update를 위해 로우 lock을 설정하려면 RS 모드 테이블 lock을 먼저 획득해야 합니다. RS,RX 간에는 어떤 조합으로도 호환이 되므로 select for update나 DML 문 수행 시 이들 간에 테이블 lock에 의한 경합은 절대 발생되지 않습니다. 다만, 같은 로우를 갱신하려 할 때 로우 lock에 의한 경합은 발생합니다.
오라클은 테이블 lock도 Enqueue로 구현하였으며, TM Enqueue라고 부릅니다. 테이블 lock을 TM Lock이라고 부르기도 합니다.
오라클에서 말하는 테이블 lock은 획득한 선행 트랜잭션이 해당 테이블에서 현재 어떤 작업을 수행 중인지를 알리는 일종의 flag입니다. 그리고 위에서 본 것 처럼 여러가지 모드가 있고, 각 모드에 따라 후행 트랜잭션이 수행할 수 있는 작업의 범위가 결정됩니다. 그 푯말에 기록된 lock모드와 후행 트랜잭션이 현재 하려는 작업내용에 따라 진행여부가 결정됩니다. 진행할 수 없다면 기다릴지, 아니면 작업을 포기할지 진로를 결정해야 합니다(nowait할지 wait 3할지 등등). 기다려야 한다면, TM Enqueue 리소스 대기자 목록에 Lock 요청을 등록하고 대기합니다.
DDL문을 이용해 테이블 구조를 변경하려는 세션은 해당 테이블에 TM lock이 설정돼 있는지를 먼저 확인합니다. TM Lock을 RX 모드로 성정한 트랜잭션이 하나라도 있으면 현재 테이블을 갱신 중인 트랜잭션이 있다는 신호입니다. 따라서 ORA-00054메세지를 던지고 작업을 멈춥니다.
DDL문이 먼저 수행 중일때는, DML문을 수행하려는 세션이 TX lock을 언으려고 대기할 것입니다. 이때 enq : TX - contention 이벤트가 발생합니다.
(9) Lock을 푸는 열쇠, 커밋
블로킹은 우리가 흔히 알고 있는 lock경합이 발생해 특정 세션이 작업을 진행하지 못하고 멈춰 선 경우를 말합니다. 이것을 해소하는 방법은 커밋또는 롤백뿐입니다.
교착상태는 두세션이 각각 lock을 설정한 리소스를 서로 액세스하려고 마주보고 진행하는 상황을 말하며 둘 중 하나가 뒤로 물러나지 않으면 영영 풀릴 수 없습니다.
오라클에서 교착상태가 발생하면 이를 먼저 인지한 세션이 문장 수준 롤백을 진행한 후에 아래 에러메시지를 던집니다. 교착상태를 발생시킨 문장 하나만 롤백하는 것입니다.
ora-00060: deadlock deteced while waiting for resource
이제 교착상태는 해소됐지만 블로킹상태는 계속됩니다. 이 메시지를 받은 세션은 커밋 또는 롤백을 결정하여 블로킹을 해소해야 합니다.
불필요하게 트랜잭션을 길제 정의하지 않도록 주의해야 합니다. 트랜잭션이 너무 길면 롤백할때 너무 많은 시간이 걸릴 수 있습니다. Undo 세그먼트가 고갈되거나 Undo 세그먼트 경합을 유발할 수 있습니다.
반대로 불필요하게 트랜잭션을 너무 자주 수행하면 sanpshot too old 에러를 유발할 가능성이 높아지고 그에 앞서 LGWR 로그 버퍼를 비우는 동안 발생하는 log file sync 대기 이벤트 때문에 성능 저하 현상을 겪을 수 있습니다.
잦은 커밋때문에 성능이 느리다면 10gR2부터 제공되는 비동기식 커밋기능을 활용하는 방안을 검토할 수 있습니다.
wait(default) : LGWR가 로그버퍼를 파일에 기록했다는 완료 메시지를 받을 때까지 대기하며 그 동안 log file sync 이벤트가 발생(동기식 커밋)
nowait : LGWR의 완료 메시지를 기다리지 않고 바로 다음 트랜잭션을 진행하므로 log file sync가 발생하지 않음(비동기식 커밋)
immediate(default) : 커밋 명령을 받을 때마다 LGWR이 로그 버퍼를 파일에 기록
batch : 세션 내부에 트랜잭션 데이터를 일정량 버퍼링했다가 일괄 처리함
commit write immediate wait;
commit write immediate nowait;
commit write batch wait;
commit write batch nowait;
4가지 조합으로 사용할 수 있습니다.
지금까지의 커밋은 트랜잭션 데이터가 데이터베이스에 안전하게 저장됨을 보장합니다. 하지만 비동기식 커밋옵션을 사용하면 트랜잭션 커밋 직후 인스턴스에 문제가 생기거나 redo 로그가 위치한 파일 시스템에 문제가 생겨 쓰기 작업을 진행할 수 없게 되면 커밋이 정상적으로 완료되지 못할 수 있습니다. 트랜잭션에 의해 생성되는 데이터 중요도에 따라 이 신기능의 활용 여부를 결정해야 합니다.
'스터디 > 오라클 성능고도화 원리와 해법1' 카테고리의 다른 글
CH03.오라클 성능 관리 - 03.SQL 트레이스 (0) | 2020.01.01 |
---|---|
CH03.오라클 성능 관리 - 01.Explain plan 02.AutoTrace (0) | 2019.12.31 |
CH2. 트랜잭션과 LOCK - 03. 비관적 vs. 낙관적 동시성 제어, 04.동시성 구현 사례 (0) | 2019.12.28 |
CH2. 트랜잭션과 LOCK - 01.트랜잭션 동시성 제어, 02.트랜잭션 수준 읽기 일관성 (0) | 2019.12.25 |
CH1.오라클 아키텍처 - 10.대기 이벤트, 11. Shared Pool (0) | 2019.12.22 |
댓글