1. 들어가며
이전에 트랜잭션 격리 수준과 그에 따른 문제점에 대해 살펴보며, 트랜잭션이 동시에 수행될 때 발생할 수 있는 문제점에 대해 알아보았다.
트랜잭션들이 동시에 수행될 때, 일관성을 해치는 것을 방지하기 위해 데이터 접근을 제어하는 동시성 제어가 필요하다.
동시성 제어를 위해 데이터에 잠금을 거는 락 기법을 사용할 수 있다. 데이터베이스에 따라 다양한 락이 존재하지만 크게 공유 락과 배타 락으로 나눌 수 있다.
2. 공유 락(Shared Lock, S-Lock)
읽기 락(Read Lock)으로도 불린다.
공유 락이 걸린 데이터에 대해서는 읽기 연산만 실행 가능하며, 쓰기 연산을 불가능하다.
또한, 공유 락이 걸린 데이터에 대해서는 다른 트랜잭션도 공유 락을 획득할 수 있다. 즉, 두 트랜잭션이 해당 데이터를 동시에 읽는 것이 가능하다.
하지만, 공유 락이 걸린 데이터에 배타 락을 거는 것을 불가능하다.
3. 배타 락(Exclusive Lock, X-Lock)
쓰기 락(Write Lock)으로도 불린다.
배타 락을 획득한 트랜잭션은 해당 데이터에 대해 읽기 연산과 쓰기 연산 모두 실행할 수 있다.
하지만, 다른 트랜잭션은 배타 락이 걸린 데이터에 대해 읽기 연산이나 쓰기 연산을 수행할 수 없다. 즉, 배타 락을 실행한 트랜잭션만 해당 데이터에 대한 독점권을 가지는 것이다.
공유 락과 배타 락의 양립 가능성을 표로 정리하면 아래와 같다.
S-Lock | X-Lock | |
S-Lock | ⭕️ | ❌ |
X-Lock | ❌ | ❌ |
4. 블로킹(Blocking)과 교착 상태(Deadlock)
다른 트랜잭션에 의해 락이 걸려서 데이터에 접근하지 못할 때, 즉 두 트랜잭션의 락이 양립 불가능할 때 해당 트랜잭션은 작업을 진행하지 못하고 대기 상태가 되며, 이를 블로킹(Blocking) 상태라고 한다.
만약 두 개의 트랜잭션이 각각의 트랜잭션이 가지고 있는 데이터의 Lock 획득을 시도하면, 둘 다 블로킹 상태가 되어 무한 대기하게 되는 문제가 발생하며, 이를 교착 상태(Deadlock)이라고 한다.
아래 예시를 보자.
- 트랜잭션 1이 리소스 1에 대한 배타 락을 획득한다
- 트랜잭션 2가 리소스 2에 대한 공유 락을 획득한다
- 트랜잭션 1이 리소스 1을 점유하고 있는 상태에서, 리소스 2에 대한 배타 락 획득을 시도하지만 얻을 수 없어 블로킹 상태에 진입한다
- 트랜잭션 2가 리소스 2를 점유하고 있는 상태에서, 리소스 1에 대한 공유 락 획득을 시도하지만 얻을 수 없어 블로킹 상태에 진입한다
5. 교착 상태 해결 방법
교착 상태를 해결할 수 있는 방법은 예방 기법, 회피 기법(Wait-Die 방식, Wound-Wait 방식), 낙관적 병행 기법, 빈도 낮추기가 있다.
5-1. 예방 기법
트랜잭션이 시작되기 전에 필요한 모든 데이터에 대한 락을 획득한다.
하지만 데이터가 많이 필요한 경우, 모든 데이터를 잠궈야 하므로 트랜잭션의 병행성이 떨어진다. 또한, 몇몇 트랜잭션은 계속 작업을 처리하지 못하는 기아 상태가 발생할 수 있다.
5-2. 회피 기법
자원을 할당할 때 타임스탬프를 활용하여 교착 상태가 일어나지 않도록 회피한다. 회피 방법으로는 Wait-Die 방식과 Wound-Wait 방식이 있다.
Wait-Die 방식
다른 트랜잭션이 데이터를 점유하고 있을 때 먼저 들어온 트랜잭션이라면 대기(wait)하고, 나중에 들어온 트랜잭션이라면 포기(die)하고 나중에 다시 요청한다.
오래된 프로세스에게 대기의 기회를 제공하고, 최신 프로세스는 자주 복귀하기 때문에 오버헤드의 가능성이 있다.
Wound-Wait 방식
다른 트랜잭션이 데이터를 점유하고 있을 때 먼저 들어온 트랜잭션이 접근하면 선점(wound)하고, 나중에 들어온 트랜잭션이라면 대기(wait)한다.
5-3. 낙관적 병행 기법
트랜잭션이 실행되는 동안에는 검사를 하지 않고, 트랜잭션이 다 실행된 이후에 검사하여 문제가 있다면 되돌린다.
5-4. 빈도 낮추기
교착상태의 빈도를 낮추기 위해 아래와 같은 방법을 시도한다.
- 트랜잭션을 자주 커밋
- 트랜잭션들이 동일한 테이블 순으로 접근
- 읽기 잠금의 사용을 피한다. (SELECT ~ FOR UPDATE)
- 테이블 단위의 락을 획득해 갱신을 직렬화
- 테으블의 복수행을 복수의 연결에서 순서 없이 갱신하면 교착 상태가 자주 발생한다