이번 포스팅에서는 트랜잭션의 격리수준에 대한 내용을 담고 있습니다.
미국의 서부 개척 시대에 주점들은 손님을 더 많이 끌어들이기 위해 공짜 점심 이벤트를
여는 문화가 있었다고 합니다. 하지만 이내 몰려든 손님들은 자신들이 마시는 술값에 점심값이
포함되어 있다는 것을 깨닫게 되어 "공짜 점심은 없다" 라는 격언이 등장하게 되었습니다.
술집을 운영하는 사장의 입장에선 손님들을 끌어들이기 위해 점심을 무료로 제공하자니
술값을 올려야하고, 점심값을 받자니 마케팅 효과가 떨어지게 되는것이죠.
이러한 Trade-Off는 앞서 포스팅했던 트랜잭션의 Isolation Level(격리 수준)에서도
나타납니다. 격리 수준을 높이면 접근 제어의 수준은 강해지지만 동시처리 성능이 낮아지고,
격리 수준을 낮추면 동시처리 성능은 올라가지만 접근 제어의 수준이 약해집니다.
이번시간에는 이런 트랜잭션의 격리수준은 단계별로 어떻게 이루어지고, 각각의 단계에 따라
어떠한 특징을 보이는지 알아보도록 하겠습니다.
트랜잭션 격리수준(Isolation Level) 이란 동시에 여러 트랜잭션이 처리될 때,
트랜션이 얼마나 서로 얼마나 고립되어 있는지를 나타내는 것입니다.
쉽게 말해, 특정 트랜잭션이 다른 트랜잭션에 변경한 데이터를 볼 수 있도록
허용할지 말지를 결정하는 것이라고 할 수 있습니다.
격리수준은 크게 아래의 4개로 나뉩니다.
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE
하나씩 알아보도록 하겠습니다.
커밋되지 않은 데이터에 접근이 가능한 수준
1. 트랜잭션 1 START
2. 트랜잭션 2 START
3. 트랜잭션 1 이 ID = 1, VAL = "MIN"인 데이터를 KIM으로 변경
4. 트랜잭션 2 가 ID = 1 을 조회, VAR = "KIM"이 조회됨
5. 트랜잭션 1 Commit , 트랜잭션 2 Commit
위 그림과 같이 Transaction 1 이 수행이 시작되어 1번 인덱스에 저장된 데이터를 업데이트하고
Commit을 통해 트랜잭션을 완료하기 전에 다른 트랜잭션이 1번 아이디의 데이터에 접근할
수 있는 상황을 말합니다.
이러한 상황에서 발생할 수 있는 문제로는 Dirty Read
가 있습니다.
ID = 1
은 다시 MIN이VAR = 'KIM'
으로 인식하게 되는 문제가 발생합니다.특정 쿼리가 요청되는 시점에 어떤 트랜잭션에 의해 이미 커밋된 데이터들만 조회할 수 있는 수준
순서
1. 트랜잭션 1 START
2. 트랜잭션 1이 ID = 1인 데이터의 VALUE를 KIM으로 변경했다.
3. 트랜잭션 2 START
4. 트랜잭션 2가 ID = 1인 데이터를 조회한다. MIN이 검색된다.
5. 트랜잭션 1 Commit & END
6. 트랜잭션 2가 ID = 1인 데이터를 조회한다. KIM이 검색된다.
7. 트랜잭션 2 커밋을 하고 종료한다.
커밋된 데이터만 읽어 오기 때문에 LV.0과의 차이점은 첫번째 SELECT문에서
KIM이 아닌 MIN이 조회된다는 것이 가장 큰 차이점입니다.
하지만 LV.1에서는 Non-Repeatable Read
문제가 발생할 수 있습니다.
하나의 트랜잭션이 같은 값을 조회할 때 다른 값이 검색되는 현상입니다.
위의 그림에서 트랜잭션 2의 첫 번째 조회에는 MIN이, 두 번째 조회에는 KIM이 검색되고 있습니다.
이와 같이 하나의 트랜잭션내에서 같아야할 정보가 달라지게 되는 문제가 발생하는 것이
Non-Repeatable Read입니다.
트랜잭션이 시작되고 종료되기 전까지 한 번 조회한 값은 계속 같은 값이 조회되는 격리 수준
REPEATABLE READ
트랜잭션 시작 전에 커밋된 내용에 한해서만 조회하는 방식의 격리수준입니다.
데이터를 변경하려고 하면 UNDO
영역에 백업해두고 실제 레코드를 변경하게 됩니다.
Lv.2에서는 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이 시작하던 시점에선 테이블에 없던 데이터입니다.
트랜잭션이 특정 테이블을 읽으면 다른 트랜잭션은 그 테이블의 데이터를 접근할 수 없는 수준
가장 강력한 격리 수준이며 데이터 정합성을 가장 잘 보장합니다.
그러나 동시 처리 성능이 가장 떨어집니다.
이 격리 수준에서는 위에서 언급했던 Dirty Read
, Non-Repeatable Read
,
Phantom Read
와 같은 정합성 문제가 전혀 발생하지 않습니다.
그렇다면 어떤 격리수준으로 설정해주는 것이 적합할까요?
정해진 답은 없습니다.
데이터 정합성과 동시 처리 성능은 반비례하기 때문에 어떤 격리 수준이 무조건 좋다 나쁘다를
말하기는 어렵습니다. 데이터를 어떻게 다룰지에 따라 적절한 전략을 선택하는 것이 중요합니다.
트랙잭션은 알고 있었지만 격리 수준이 있다는 건 몰랐는데 유용한 글이네요!