본문 바로가기

Study

트랜잭션 격리 수준(Isolation Level)과 동시성 제어

동시성 제어

동시성 제어란, 동일한 데이터에 두 개 이상의 트랜잭션이 동시에 일어날 때 데이터의 정합성을 위해 트랜잭션의 작업 순서를 조정하는 것을 말한다. 같은 데이터에 일어나는 트랜잭션에 대해 적절한 제어를 하지 않으면, Dirty Read, Non-Repeatable Read, Phantom Read 등 여러 문제가 발생한다. 일반적으로 읽기 작업은 여러 트랜잭션이 동시에 발생해도 별다른 문제를 야기하지 않지만, 어느 하나 이상의 트랜잭션에서 쓰기 작업을 동반할 때 다른 트랜잭션의 읽기와 쓰기에 여러 문제가 발생하기 때문에 동시성 제어가 필요하다. 동시성 제어는 데이터 락(data Lock) 을 통해 진행되는데, 데이터 락에는 읽기를 할 때 사용하는 공유 락(Share Lock), 읽기 또는 쓰기를 할 때 사용하는 베타 락(Exclusive Lock) 이 있다. 이 락을 사용한 Locking 기법으로 트랜잭션의 동시접근을 제어하는데, 동시성 제어가 정확히 어떤 방법으로 진행되며 누가 주체가 되어 진행하는지 알아보도록 하자.

 

 

락(Lock)

동시성을 제어하는 데는 여러 방법이 있지만 일반적으로는 트랜잭션과 락을 이용한 동시성 제어를 사용한다. 트랜잭션의 동시성 제어 개념을 이해하기 위해선 락(Lock) 에 대한 개념이 필수적인데, 락에는 공유 락(읽기에 사용)과 베타 락(쓰기에 사용)이 있다. 공유 락이 걸린 데이터는 다른 트랜잭션의 공유 락 요청을 허용한다. 베타 락이 걸린 데이터는 다른 트랜잭션의 어떤 락 요청도 허용하지 않는다. 락에 대한 규칙을 자세히 알아보면 다음과 같다.

- 이 글에서는 트랜잭션 격리수준 옵션을 사용한 동시성 제어만을 다룬다. 단시간에 요청이 많이 몰릴 수 있는 서비스의 경우, 트랜잭션 격리수준 정의만으로 해결할 수 없는 동시성 문제가 불가피하게 발생한다. 이런 경우는 redis 등을 사용한 분산 락, DB 락 등을 사용하여 더 엄격하게 동시성을 제어할 수 있다.

Lock 을 사용하는 규칙

1. 트랜잭션이 읽기 작업을 할 경우, 데이터에 공유 락(LS)을 요청한다.
2. 트랜잭션이 읽기 또는 쓰기 작업을 할 경우, 데이터에 베타 락(LX) 을 요청한다.
3. 공유 락(LS) 이 걸려있는 데이터에 대해, 다른 트랜잭션이 공유 락(LS) 을 걸 수 있으나 베타 락(LX) 은 걸 수 없다.
4. 베타 락(LX) 이 걸려있는 데이터에 대해, 다른 트랜잭션이 어떤 락도 걸 수 없다.
5. 락을 허용받지 못한 트랜잭션은 대기상태가 된다.

 

데이터베이스는 로킹을 사용함에 있어서 데이터 정합성 문제를 해결하기 위해 Two-Phase-Locking (2단계 로킹)기법을 사용한다. 2단계 로킹이란 트랜잭션이 lock 요청만 수행하는 Growing Phase, 트랜잭션이 unlock 요청만 수행하는 Shrinking Phase 의 두 단계로 락을 걸고 해제하는 시점을 나눠서 관리하는 방법이다. 2단계 로킹 기법은 트랜잭션의 동시성을 어느 정도 보장하도록 도움을 주지만, 데드락(교착상태) 에 빠질 위험이 있어 이를 관리해야 한다.

 

 

트랜잭션 격리 수준

DBMS 는 동시에 발생하는 트랜잭션에 대해서 락을 보다 잘 사용하기 위해 4가지 수준의 격리 수준(트랜잭션이 서로 격리되어있는 수준)을 제공한다. 각 단계는 데이터에 공유 락과 베타 락을 걸어 주는 시기와 범위를 적절하게 조정하면서 동시성을 제어한다. 4 단계의 격리 수준과 발생할 수 있는 문제는 다음과 같다.

1) READ uncommitted - Level 0
 격리 수준이 가장 낮은 상태로, 변경 내용의 commit 이나 rollback 여부에 상관 없이 값을 읽어올 수 있다. 자신(트랜잭션)의 데이터에 공유 락을 걸지 않지만, 베타 락은 걸려 있는 상태다.(조회에 한해서는 공유 락을 걸지 않지만, 갱신에 대해서는 베타 락을 설정) 또한 락이 걸린 다른 트랜잭션의 데이터를 공유 락 없이 조회할 수 있는 상태이다.(조회하는 트랜잭션(자신)이 공유 락을 걸지 않기 때문에, 다른 트랜잭션의 락이 걸린 데이터라도 접근할 수 있다.)

- 발생할 수 있는 문제 : Dirty Read  →  트랜잭션이 커밋되기 전의 데이터를 읽어서 발생하는 문제. T1 트랜잭션이 A 값을 A1 로 변경하였다가 롤백했을 때, 롤백하기 전 시점에 T2 트랜잭션이 A1 값을 읽어옴으로써 데이터 정합성에 문제가 발생함.

그 밖에 Non Repeatable Read, Phantom Read 문제 발생.

2) READ committed - Level 1
 트랜잭션이 완료되고 commit 된 데이터만 다른 트랜잭션에서 접근할 수 있도록 허용된 격리 수준이며, 일반적으로 DBMS 에서 기본적으로 설정된 격리 수준이다. 자신의 데이터를 조회할 때 공유 락을 걸지만, 트랜잭션이 끝나기 전이라도 참조가 끝난 후에 바로 공유 락을 해지할 수 있다. 다른 트랜잭션에 대해서는 공유 락이 걸린 데이터는 조회가 가능하지만 베타 락이 걸린 데이터는 조회가 불가능하다.(조회하는 트랜잭션(자신)이 공유 락을 걸기 때문에, 다른 트랜잭션의 락이 걸린 데이터에 접근하는 데 제한이 있다. → 공유 락과 베타 락의 기본 규칙을 따라감)

- 발생할 수 있는 문제 : Non-Repeatable Read  →  트랜잭션이 처음 조회한 데이터와 두번째로 조회한 데이터가 불일치하는 현상. T1 트랜잭션이 처음 A 를 조회하고 난 뒤 두 번째 조회를 하기 전에 T2 트랜잭션이 A 를 A1 로 변경하고 커밋한 경우, T1 트랜잭션은 같은 데이터에 대해 여러 번 조회한 것임에도 일치하지 않는 데이터를 얻게 됨.
   →  READ committed 상태에서는 조회 후 공유 락을 해제할 수 있기 때문에 다른 트랜잭션에서 베타 락을 걸 수 있다.( = 변경이 가능하다.)

그 밖에 Phantom Read 문제 발생.

3) REPEATABLE READ - Level 2
자신보다 앞서 시작된 트랜잭션에 대해서는 commit된 데이터만 읽을 수 있으며, 자신보다 나중에 시작된 트랜잭션에 대해서는 Undo 영역에 백업된 데이터를 읽도록 허용된 격리 수준이다. Non-Repeatable Read 문제가 발생하지 않지만 Phantom Read 문제가 발생할 수 있고, 백업 레코드가 많아질수록 성능이 떨어지는 단점이 있다. 조회(공유 락)와 수정(베타 락) 모두 트랜잭션이 끝날때까지 락이 걸린 상태를 유지한다. 즉, 자신이 접근하는 데이터에 대해서는 어떤 트랜잭션도 수정이 불가능하다. MySQL 에선 REPEATABLE READ 가 기본으로 설정된 격리 수준이며, 갭 락의 존재로  Phantom Read 문제가 거의 발생하지 않는다고 한다.

- 발생할 수 있는 문제 : Phantom Read  →  자신이 조회하는 데이터에 다른 트랜잭션이 쓰기(Insert) 를 했을 때, 처음 조회한 데이터와 두번째 조회한 데이터가 불일치하는 현상. Non-Repeatable Read 문제와 유사하지만 Non-Repeatable Read 문제는 '데이터가 변경된 것을 조회하는 것' 이고 Phantom Read 는 '다른 데이터가 삽입된 것을 조회하는 것' 이다.
  →  Phantom Read 가 왜 발생하는가 생각할 수도 있지만, 자신이 락을 걸어놓은 데이터에 Update(갱신)문은 '이미 락이 걸린 데이터를 변경' 하는 것이기 때문에 불가능하지만 Insert(삽입)문은 새로운 데이터를 추가하는 것이므로, 테이블이나 DB 단위로 락이 걸려있지 않은 한 락으로 막을 수 없다. 이 상태에서 락의 범위를 테이블로 확장한 것이 다음 격리수준인 SERIALIZABLE 레벨이다.

4) SERIALIZABLE - Level 3
트랜잭션의 고립성을 철저하게 준수하는, 가장 엄격한 형태의 격리 수준으로 모든 트랜잭션이 직렬되게 동작한다. 일관성 문제가 전혀 발생하지 않지만 동시성이 떨어지므로 잘 사용되지 않는 격리 수준이다. 똑같이 조회 시 공유 락을, 변경 시 베타 락을 걸지만 락을 테이블 단위로 걸기 때문에 데이터 정합성에 어떤 문제도 발생하지 않는다.

 

 

각 격리 수준에서 락이 어떤 시점에 적용되고 해제되는지를 자세히 살펴보면, 결국 공유 락과 베타 락의 기본 규칙에 따라 각 단계의 동작이 정의되고 나타나는 문제도 왜 발생하는지 이해할 수 있을 것이다. 트랜잭션의 동시성 제어에는 대부분 락이 사용되므로, 공유 락과 베타 락의 기본적인 규칙은 숙지해 두는 것이 이해에 큰 도움이 될 것이다.

 

 

'Study' 카테고리의 다른 글

계산기  (0) 2024.04.28
직렬화(Serialization) 와 역직렬화(Deserialization)  (0) 2024.04.26
트랜잭션의 개념과 사용  (0) 2024.04.11
Spring PSA  (0) 2024.02.29
DI 와 Spring 의 의존성 주입 방법  (0) 2024.02.16