오라클 Lock
- 공유 리소스와 사용자 데이터 보호를 위해 DML Lock, DDL Lock, 래치, 버퍼 Lock, 라이브러리 캐시 Lock/Pin 등 다양한 Lock 사용
- 래치는 SGA에 공유된 각종 자료구조를 보호하기 위해 사용
- 버퍼 Lock은 버퍼 블록에 대한 엑세스를 직렬화하기 위해 사용
- 라이브러리 캐시 Lock과 Pin은 라이브러리 캐시에 공유된 SQL 커서와 PL/SQL 프로그램을 보호하가 위해 사용
- 애플리케이션 개발에서 가장 중요하게 다뤄야 할 Lock은 DML Lock
- 다중 트랜잭션에 동시에 엑세스하는 사용자 데이터와 무결성을 보호
- 테이블 Lock과 로우 Lock
DML 로우 Lock
- 두 개의 동시 트랜잭션아 같은 로우를 변경하는 것을 방지
- 하나의 로우를 변경하려면, 로우 Lock을 먼저 설정해야
- DML 로우 Lock은 배타적 모드 사용
- UPDATE, DELETE를 진행 중인(아직 커밋하지 않은) 로우를 다른 트랜잭션이 UPDATE, DELETE 불가능
- INSERT에 대한 로우 Lock 경합은 Unique 인덱스가 있을 때만 발생
- Unique 인덱스가 있는 상황에서 두 트랜잭션이 같은 값을 입력하려고 할 때, 블로킹 발생
- 블로킹 발생시, 후행 트랜잭션을 기다렸다가 선행 트랜잭션이 커밋하면 INSERT에 실패하고, 롤백하면 성공
- 두 트랜잭션이 서로 다른 값을 입력하거나 Unique 인덱스가 아예 없으면 INSERT에 대한 로우 Lock 경합 없음
- Unique 인덱스가 있는 상황에서 두 트랜잭션이 같은 값을 입력하려고 할 때, 블로킹 발생
- MVCC 모델을 사용하는 오라클은 SELECT 문에 로우 Lock을 사용하지 않음
- 오라클은 다른 트랜잭션이 변경한 로우를 읽을 때 복사본 블록을 만들어 쿼리가 시작된 시점으로 되돌려서 읽음
- 변경이 진행 중인(아직 커밋하지 않음) 로우를 읽을 때도 Lock이 풀릴 때까지 기다리지 않고 복사본을 만들어서 읽음
- MVCC를 사용하지 않는 DBMS는 SELECT문에 공유 Lock 사용
- 공유 Lock 끼리는 호환돼, 두 트랜잭션이 같이 Lock 설정 가능
- 공유 Lock과 배타적 Lock은 호환되지 않기 때문에, DML과 SELECT가 서로 진행 방해 가능
- 다른 트랜잭션이 읽고 있는 로우를 변경하려면, 다음 레코드로 이동할 때까지 기다려야 함
- 다른 트랜잭션이 변경 중인 로우를 읽으려면, 커밋할 때까지 기다려야 함
- 오라클에서 DML과 SELECT는 서로 진행을 방해하지 않음, SELECT 끼리도 마찬가지
- DML 로우 Lock에 의한 성능 저하를 방지하려면, 온라인 트랜잭션을 처리하는 주간에 Lock을 필요 이상으로 오래 유지하지 않도록 커밋 시점을 조절해야 함
- 트랜잭션이 빨리 일을 마치도록, 즉 Lock이 오래 지속되지 않도록 관련 SQL을 모두 튜닝해야 함
DML 테이블 Lock
- DML 로우 Lock을 설정하기 앞서 테이블 Lock을 먼저 설정
- 현재 트랜잭션이 갱신 중인 테이블 구조를 다른 트랜잭션이 변경하지 못하게 막기 위해
- Lock 모드간 호환성(Compatibility)
- RS: row share
- RX: row exclusive
- S: share
- SRX: share row exclusive
- X: exclusive
| Null | RS | RX | S | SRX | X | |
|---|---|---|---|---|---|---|
| Null | ○ | ○ | ○ | ○ | ○ | ○ |
| RS | ○ | ○ | ○ | ○ | ○ | |
| RX | ○ | ○ | ○ | |||
| S | ○ | ○ | ○ | |||
| SRX | ○ | ○ | ||||
| X | ○ |
- 선행 트랜잭션과 호환되지 않는 모드로 테이블 Lock을 설정하려면 후행 트랜잭션은 대기하거나 작업 포기해야 함
- INSERT, UPDATE, DELETE, MERGE문을 위해 로우 Lock을 설정하려면 해당 테이블에 RX 모드 테이블 Lock을 먼저 설정해야 함
- SELECT FOR UPDATE 문을 위해 로우 Lock을 설정하려면 10gR1 이하는 RS, 10gR2 이상은 RX 모드 테이블 Lock을 먼저 설정해야 함
- RS, RX 간에는 어떤 조합으로도 호환되므로 SELECT FOR UPDATE나 DML문 수행시 테이블 Lock에 의한 경합 발생 안 함
- 같은 로우 갱신하려 할 때만 로우 Lock에 의한 경합 발생
- 테이블 Lock이 테이블 전체에 Lock을 거는 것이 아님
- 테이블 Lock을 설정한 트랜잭션이 해당 테이블에서 수행 중인 작업을 알려주는 일종의 Flag
- 여러 종류가 있고, 그에 따라 후행 트랜잭션이 수행할 수 있는 작업 범위가 결정
- 진행할 수 없다면 기다릴지, 작업을 포기할지 진로를 결정해야 함
- 기다린다면 대기자 목록에 Lock 요청을 등록하고 대기
- DDL을 이용해 테이블 구조를 변경하려는 트랜잭션은 테이블에 TM Lock 설정돼 있는지 확인
- TM Lock을 RX모드로 설정한 트랜잭션아 하나라도 있다면, 테이블을 갱신 중인 트랜잭션이 있다는 신호
- ORA-00054 반환하고 작업을 멈춤
- TM Lock을 RX모드로 설정한 트랜잭션아 하나라도 있다면, 테이블을 갱신 중인 트랜잭션이 있다는 신호
- DDL문이 먼저 수행 중일때는, DML문을 수행하려는 트랜잭션이 기다림
- 진행할 수 없다면 기다릴지, 작업을 포기할지 진로를 결정해야 함
- 여러 종류가 있고, 그에 따라 후행 트랜잭션이 수행할 수 있는 작업 범위가 결정
- 테이블 Lock을 설정한 트랜잭션이 해당 테이블에서 수행 중인 작업을 알려주는 일종의 Flag
대상 리소스가 사용 중일때, 진로 선택
- Lock을 얻고자 하는 리소스가 사용 중일때, 프로세스는 아래 세 가지 방법 중 하나를 택함
- Lock이 해제될 때까지 대기
- 일정 시간만 기다리다 포기
- 기다리지 않고 작업을 포기
- SELECT FOR UPDATE는 사용자가 위 세 가지 옵션을 모두 선택할 수 있음
select * from t for update;
select * from t for update wait 3;
select * from t for update nowait;
- DML을 수행할 때 묵시적으로 테이블 Lock을 설정하는데, 이때는 첫 번째 방법을 선택
- Lock Table로 명시적으로 테이블 Lock을 설정할 때도 기본적으로 기다리는 방법 선택
- NOWAIT 옵션으로 곧바로 작업을 포기하도록 사용 가능
- Lock Table로 명시적으로 테이블 Lock을 설정할 때도 기본적으로 기다리는 방법 선택
- DDL을 수행할 때도 내부적으로 테이블 Lock을 설정하는데, NOWAIT 옵션이 자동으로 지정
Lock을 푸는 열쇠, 커밋
- 블로킹(Blocking)은 선행 트랜잭션이 설정한 Lock 때문에 후행 트랜잭션이 작업을 진행하지 못하고 멈춰 있는 상태
- 커밋 또는 롤백으로 해소
- 교착상태(Deadlock)은 두 트랜잭션이 각각 특정 리소스에 Lock을 설정한 상태에서 맞은편 트랜잭션이 Lock을 설정한 리소스에 또 Lock을 설정하려고 진행한느 사오항
- 둘 중 하나가 물러나지 않으면 영영 해결 불가
- 데드락이 발생하면, 이를 먼저 인지한 트랜잭션이 문장 수준 롤백을 진행 후 ORA-00060
- 교착상태를 발생시킨 문장 하나만 롤백
- 교착상태는 해결됐지만 블로킹 상태
- 에러를 받은 트랜잭션은 커밋 또는 롤백을 결정해야 함
- 프로그램에서 예외처리를 하지 않는다면 대기 상태를 지속
- 교착상태를 발생시킨 문장 하나만 롤백
- 오라클은 데이터를 읽을 때 Lock을 사용하지 않으므로 타 DBMS 대비 Lock 경합이 적게 발생
- 읽는 트랜잭션의 진행을 막는 부담감이 없으므로, 필요한 만큼 트랜잭션을 충분히 길게 가져갈 수 있음
- 불필요하게 트랜잭션이 길게 정의되지 않게 주의
- 너무 길면, 트랜잭션을 롤백해야 할 때 너무 많은 시간이 걸림
- Undo 세그먼트 고갈 또는 경합 유발 가능
- 같은 데이터를 갱신하는 트랜잭션이 동시 수행되지 않도록 애플리케이션을 설계해야
- DML Lock 때문에 동시성이 저하되지 않도록 적절한 시점에서 커밋
- 불필요하게 커밋을 너무 자주 수행하면 서버 프로세스가 LGWR에게 로그 버퍼를 비우도록 요청하고 sync 방식으로 기다리는 횟수가 늘기 때문에 성능이 느려짐
10gR2부터 비동기식 커밋과 배치 커밋 활용 가능
- WAIT(Default): LGWR가 로그버퍼를 파일에 기록했다는 완료 메시지를 받을 때까지 대기
- sync
- NOWAIT: LGWR의 완료 메시지를 기다리지 않고 바로 다음 트랜잭션을 진행
- async
- IMMEDIATE(Default): 커밋 명령을 받을 때마다 LGWR가 로그 버퍼를 파일에 기록
- BATCH: 세션 내부에서 트랜잭션 데이터를 일정량 버퍼링했다가 일괄 처리
COMMIT WRITE IMMEDIATE WAIT ; COMMIT WRITE IMMEDIATE NOWAIT ; COMMIT WRITE BATCH WAIT ; COMMIT WRITE BATCH NOWAIT ;- WAIT(Default): LGWR가 로그버퍼를 파일에 기록했다는 완료 메시지를 받을 때까지 대기
트랜잭션 동시성 제어
- 비관적 동시성 제어(Pessimistic Concurrency Control)은 사용자들이 같은 데이터를 동시에 수정할 것으로 가정