트랜잭션 격리 수준
개발을 하다 보면 여러 스레드에서 동시에 하나의 자원에 접근하는 경우가 있다.
접근을 적절하게 제한하지 않는다면 생각지 못한 버그가 발생할 수 있다.
게다가 이런 경우는 디버깅하기도 굉장히 어렵다.
데이터베이스도 N개의 트랜잭션을 동시에 처리하다 보면 같은 데이터에 접근할 수 있다.
트랜잭션의 목적은 로직의 흐름 속에서 데이터를 일관되게 처리하기 위한 것이나, 동시에 같은 데이터에 write 접근을 하는 경우엔 문제가 될 수 있다.
동시성 문제를 해결하기 위해 데이터베이스는 격리 수준이라는 기능을 제공한다.
격리 수준의 종류
격리 수준은 4개로 구분된다.
- READ UNCOMMITTED
- READ COMMITTED
- REPEATABLE READ
- SERIALIZABLE
4에 가까울 수록 동시성 접근 제어의 수준이 강해지며 동시 처리 성능은 낮아진다.
접근 제어를 강하게 수행한다는 것은 트랜잭션이 끝나기 전까지 해당 데이터를 잠그고 다른 트랜잭션에서 참조하지 못하게 하는 것이기 때문이다.
READ UNCOMMITTED
커밋되지 않은 데이터에 접근이 가능한 수준으로, N개의 트랜잭션이 하나의 공유 데이터에 접근해도 전혀 보호되지 않는다.
1. 트랜잭션 1을 시작한다.
2. 트랜잭션 2를 시작한다.
3. 트랜잭션 1이 ID = 1, VAL = 'MIN'인 데이터의 VAL을 KIM으로 변경했다.
4. 트랜잭션 2가 ID = 1을 조회한다. VAL = 'KIM'이 조회되었다.
5. 트랜잭션 1, 2가 종료된다.
이 수준에서만 볼 수 있는 문제는 Dirty Read
가 있다.
Dirty Read?
- UPDATE 반영 전에 읽는 오류
- 순서 5에서 오류가 생겨 롤백이 되었다고 가정하자.
ID = 1
은 다시 MIN 이 된다. - 그러나 롤백 전인 순서 4번은 여전히
VAL = 'KIM'
으로 인식할 것이다.
- 순서 5에서 오류가 생겨 롤백이 되었다고 가정하자.
- INSERT 반영 전에 읽는 오류
- 트랜잭션 1이 특정 데이터를 INSERT한다.
- 트랜잭션 2가 그 데이터를 읽고 로직을 수행한다.
- 트랜잭션 1 수행 중 오류가 생겨 롤백된다. INSERT한 데이터가 삭제되었다.
- 그러나 트랜잭션 2는 이미 로직을 수행한 상태이다.
READ COMMITTED
커밋된 데이터만 조회할 수 있어 Dirty read
는 발생하지 않는다.
1. 트랜잭션 1을 시작한다.
2. 트랜잭션 1이 ID = 1인 데이터의 VALUE를 KIM으로 변경했다.
3. 트랜잭션 2가 시작되었다.
4. 트랜잭션 2가 ID = 1인 데이터를 조회한다. MIN이 검색된다.
5. 트랜잭션 1이 커밋을 하고 종료한다.
6. 트랜잭션 2가 ID = 1인 데이터를 조회한다. KIM이 검색된다.
7. 트랜잭션 2가 커밋을 하고 종료한다.
커밋된 데이터만을 읽어오기 때문에 READ UNCOMMITED
에서 롤백되는 경우 발생하는 문제는 생기지 않는다.
이 격리 수준 이하에선 Non-Repeatable Read
문제가 발생한다.
Non-Repeatable Read?
하나의 트랜잭션이 같은 값을 조회할 때 다른 값이 검색되는 현상이다.
위의 그림에서 트랜잭션 2의 첫 번째 조회엔 MIN이, 두 번째 조회엔 KIM이 검색되고 있다.
REPEATABLE READ
트랜잭션이 시작되고 종료되기 전까지 한 번 조회한 값은 계속 같은 값이 조회되는 격리 수준이다.
트랜잭션 시작 전에 커밋된 내용에 한해서만 조회된다.
데이터를 변경하려고 하면 UNDO
영역에 백업해두고 실제 레코드를 변경하게 된다.
이 격리 수준에서는 Non-Repeatable Read
는 발생하지 않는다.
1. 트랜잭션 1을 시작한다.
2. 트랜잭션 1이 ID = 1인 데이터를 조회한다.
3. 트랜잭션 2가 시작되었다.
4. 트랜잭션 2가 ID = 1인 데이터를 KIM으로 변경한다.
5. 트랜잭션 1이 ID = 1인 데이터를 조회한다. 트랜잭션 2의 변경 내역이 보이지 않는다.
6. 트랜잭션 2가 ID = 2인 데이터를 삽입 후 commit하여 트랜잭션을 종료한다.
7. 트랜잭션 1이 ID = 2인 데이터를 조회한다. 데이터가 정상적으로 확인된다.
8. 트랜잭션 1이 종료된다.
이 격리 수준에서는 UPDATE 한 데이터에 대해서는 정합성을 보장하지만, INSERT/DELETE 는 보장되지 않는다.
이 때문에 이 격리 수준 이하에서는 Phantom Read
문제가 발생한다.
Phantom Read?
마치 유령을 보는 것처럼 있던 데이터가 사라지거나 없던 데이터가 생기는 현상을 말한다.
바로 위 예제의 순서 7에서 트랜잭션 1이 ID = 2인 데이터를 조회하고 있다.
ID = 2는 트랜잭션 1이 시작하던 시점에선 테이블에 없던 데이터이다.
만약 순서 8에서 트랜잭션 2가 비정상 종료되어 롤백되었다고 가정한다면 (2, 'KIM') 데이터는 삽입되지 않으며
트랜잭션 1은 ID = 2인 데이터를 읽을 수 없다.
SERIALIZABLE
트랜잭션이 특정 테이블을 읽으면 다른 트랜잭션은 그 테이블의 데이터를 추가/변경/삭제할 수 없다.
가장 강력한 격리 수준이며 데이터 정합성을 가장 잘 보장한다.
그러나 동시 처리 성능이 가장 떨어진다.
이 격리 수준에서는 위에서 언급했던 Dirty Read
, Non-Repeatable Read
, Phantom Read
와 같은 정합성 문제가 전혀 발생하지 않는다.
데이터 정합성과 동시 처리 성능은 반비례하기 때문에 어떤 격리 수준이 무조건 좋다 나쁘다를 말하기는 어렵다.
데이터를 어떻게 다룰지에 따라 적절한 전략을 선택하는 것이 중요하다.
출처: https://private-space.tistory.com/97 [티끌모아 산을 쌓아보자]
'CS > DB' 카테고리의 다른 글
정규화 (0) | 2021.12.28 |
---|---|
스프링 트랜잭션과 트랜잭션 전파 (0) | 2021.12.28 |
관계형 데이터베이스 (0) | 2021.12.28 |
트랜잭션 (0) | 2021.11.21 |
[Database] 정규화(Normalization) 쉽게 이해하기 (0) | 2021.09.12 |