| Title | Category | Author |
|---|---|---|
트랜잭션 격리수준(Transaction Isolation Level) |
DB |
Jung |
트랜잭션 격리수준은 동시에 여러 트랜잭션이처리 될 때, 트랜잭션끼리 얼마나 서로 고립되어 있는지를 나타내는 척도.
commit 되지 않은 변화를 읽음
- 트랜잭션1은 x에 y를 더하는 로직
- 트랜잭션2는 y에 70을 쓰는 로직
- x = 10, y= 20일때
- 트랜잭션1이 x를 읽어오고
- 트랜잭션2가 y에 70을 썼다.
- 그 후 트랜잭션 1이 x에 y를 더하면 x = 80이 되고, y = 70이 된다.
- 트랜잭션1이 커밋을 마쳤는데, 트랜잭션2가 aborting이 떠서 rollback하면
- x = 80이되고, y = 20이 되는 무의미한 상태가 된다.
같은 데이터의 값이 달라지는 현상
- 트랜잭션 1은 x를 두 번 읽는 로직
- 트랜잭션 2는 x에 40을 더하는 로직
- 현재 x = 10
- 트랜잭션 1이 x(10)을 읽는다
- 그 후 트랜잭션 2가 x(10)을 읽고 40을 더한 후 커밋한다 -> x=50
- 이때 트랜잭션 1이 x를 다시 읽는다 50
- 그후 트랜잭션 1이 커밋
없던 데이터가 생기는 경우
- 트랜잭션1은 v가 10인 데이터를 두 번 읽는 로직
- 트랜잭션2는 t2의 v를 10으로 바꾸는 로직
| 현재 DB 상황 |
|---|
| t1(..., v= 10) |
| t2(..., v= 50) |
- 트랜잭션1이 v가 10인 데이터를 읽는다 -> t1
- 트랜잭션2가 t2의 v를 10으로 변경하고 커밋
- 다시 트랜잭션1이 v가 10인 데이터를 읽고 커밋 -> t1, t2
이런 이상 현상들을 모두 발생하지 않게 만들 수 있지만
그러면 제약사항이 만아져서 동시 처리 가능한 트랜잭션 수가 줄어들어
결국 DB 전체 처리량(throughput)이 하락하게 된다
| 트랜잭션 격리 수준 |
|---|
![]() |
-
Read Uncoimmitted
- 트랜잭션에서 처리중인 아직 커밋되지 않은 데이터를 다른 트랜잭션이 읽는 것을 허용
-
Read Committed
- 트랜잭션이 커밋되어 확정된 데이터만 다른 트랜잭션이 읽도록 허용
- 커밋 되지 않은 데이터에 대해서 실제 DB 데이터가 아닌 Undo 로그에 있는 이전 데이터를 가져오는 방식
- Oracle에서 기본적으로 사용하는 격리 수준
-
Repeatable read
- 트랜잭션이 시작되기 전에 커밋된 내용에 대해서만 읽도록 허용
반복 가능한 읽기라는 이름에서 알 수 있듯이 한 트랜잭션 내에서 읽기가 여러 번 발생해도 같은 값을 가져옴- 즉 트랜잭션 수행 중 다른 트랜잭션에 의해 데이터가 변경되어도 변경 전의 데이터로 읽음
- 트랜잭션 내에서 삭제, 변경에 대해서 Undo 로그에 넣어두고 앞서 발생한 트랜잭션에 대해서는 실제 데이터가 아닌 Undo 로그에 있는 백업데이터를 읽게 함
- MySQL에서 기본으로 사용하는 격리 수준
-
Serializable read
- 트랜잭션 내에서 쿼리를 두 번 이상 수행 할 때, 첫 번째 쿼리에 있던 레코드가 사라지거나 값이 바뀌지 않음은 물론 새로운 레코드가 나타나지 않도록 설정
- 읽기 작업에도 락이 걸려 동시성이 떨어진다.
애플리케이션 설계자는 isolation level을 통해 전체 처리량과 데이터 일관성 사이에서
특정 상황에 맞게 격리 수준을 고려하여 작성할 수 있다.
commit 되지 않은 데이터를 write 한다.
- 트랜잭션1은 x를 10으로 바꾸는 로직
- 트랜잭션2는 x를 100으로 바꾸는 로직
- 현재 x는 0
- 먼저 트랜잭션1은 x를 0에서 10으로 바꾼다 -> x = 10
- 먼저 트랜잭션2은 x를 10에서 100으로 바꾼다 -> x = 100
- 이후 트랜잭션1이 aborting이 나서 x를 0으로 rollback 한다 -> x = 0
- 이후 트랜잭션2에서 aborting이 나서 x를 10으로 rollback 한다 -> x = 10??
rollback시 정상적인 Recovery는 매우 중요하기 때문에 모든 isolation level에서
dirty write를 허용하면 안된다.
- 트랜잭션1은 x에 50을 더하는 로직
- 트랜잭션2는 x에 150을 더하는 로직
- 현재 x = 50
- 트랜잭션1이 x를 읽는다 -> 50
- 트랜잭션2가 x를 읽는다 -> 50
- 트랜잭션2가 x에 150을 더하고 커밋한다 -> x = 200
- 트랜잭션 1이 읽어온 x(50)에 50을 더하고 커밋한다 -> x = 100
트랜잭션 2번의 업데이트를 트랜잭션 1번이 덮어써버리는 경우이다.
- 트랜잭션1이 x가 y에 40을 이체한다
- 트랜잭션2가 x와 y를 읽는다
- 현재 x = 50, y = 50
- 트랜잭션1이 x를 읽는다 -> x = 50
- 트랜잭션1이 x를 10으로 변경한다 -> x = 10
- 이 타이밍에 트랜잭션 2번이 x와 y를 읽고 커밋한다, x = 10, y = 50
- 트랜잭션1은 y를 읽는다 -> y = 50
- 트랜잭션1은 y를 90으로 변경하고 커밋한다 -> x = 10, y = 90
트랜잭션 2에서 데이터 정합성이 깨진다
불일치하는 데이터를 읽기
- 트랜잭션1이 x가 y에 40을 이체한다
- 트랜잭션2가 x와 y를 읽는다
- 현재 x = 50, y = 50
- 트랜잭션2가 x를 읽는다 -> x = 50
- 트랜잭션1이 x를 10으로, y를 90으로 쭈욱 변경하고 커밋 -> x = 10, y = 90
- 그 후 트랜잭션2가 y를 읽으면 -> y = 90이 된다.
트랜잭션 2에서 x와 y의 합이 140이 된다...
불일치하는 데이터 쓰기
- 트랜잭션1은 x에서 80을 인출한다
- 트랜잭션2는 y에서 90을 인출한다
- x = 50, y = 50이며 x + y >= 0을 항상 만족해야 한다
- 트랜잭션1은 x를 읽는다 -> x = 50
- 트랜잭션1은 y를 읽는다 -> y = 50
- 트랜잭션2가 x를 읽는다 -> x = 50
- 트랜잭션2가 y를 읽는다 -> y = 50
- 트랜잭션1에서 x에서 80을 인출한다 -> x = -30, y = 50
- 트랜잭션2에서 y에서 90을 인출한다 -> x = 50, y = -40
- 그후 트랜잭션1과 트랜잭션2가 커밋이 된다.
결과적으로 제약사항을 어기게 되는 상황이 발생한다.
트랜잭션 진입때 버전을 관리하고 먼저 커밋된 트랜잭션에 대해 인정한다.
이후 커밋된 상태에서 다른 트랜잭션이 쓰기 작업이 발생하게되면 스냅샷이 달라
aborting이 발생하여 rollback 한다.
- 트랜잭션1은 x가 y에 40을 이체한다
- 트랜잭션2는 y에 100을 입금한다
- 현재 x = 50, y = 50
- 트랜잭션1이 x를 읽는다 -> 스냅샷1 x = 50, y = 50
- 트랜잭션1이 x에 10을 쓴다 -> 스냅샷1 x = 10, y = 50
- 이후 트랜잭션2는 y를 읽는다 -> 스냅샷2 x = 50, y = 50
- 트랜잭션2는 y에 150을 쓴다 -> 스냅샷2 x = 50, y = 150
- 트랜잭션 2가 커밋하면서 스냅샷2를 반영한다 -> 실제 x = 50, y = 150
- 트랜잭션1이 y를 읽는다 이때 트랜잭션2가 반영된 것이 아니라 앞서 스냅샷을 읽는다 -> 스냅샷1 x = 10, y = 50
- 트랜잭션1이 y를 90으로 변경한다 -> 스냅샷 1은 x = 10, y = 90
- 트랜잭션1이 커밋을 할 때 먼저 커밋된 트랜잭션만 인정해준다.
- 즉 이때 트랜잭션1에서 abort가 발생된다.
- 그래서 이때 스냅샷1은 사라진다
각 트랜잭션마다 특정 시점에서 스냅샷의 버전을 컨트롤하여 동시성을 제어하는 것
