트랜잭션 격리 수준이란, 동시에 여러개의 트랜잭션이 수행될 때 각 트랜잭션이 얼만큼의 고립성을 가지는지 나타내는 것.
즉, 특정 트랜잭션이 다른 트랜잭션에 변경된 데이터를 보여줄것인지에 대한 여부를 나타내는 것.
격리 수준은 4가지로 나뉜다.
특정 트랜잭션에서 변경된 데이터를 트랜잭션이 commit(), Rollback 여부와 상관없이 다른 트랜잭션에게 보여지는 격리 수준.
참조 : https://nesoy.github.io/articles/2019-05/Database-Transaction-isolation
다른 트랜잭션이 commit()되기 전의 데이터도 조회가 가능하기 때문에 Dirty Read
, Phantom Read
, Non-Repetable Read
가 발생한다.
Dirty Read
란 다른 트랜잭션의 중간 처리 작업의 결과를 볼 수 있는 현상.
즉, 트랜잭션이 커밋되기 전에 수정된 데이터를 다른 트랜잭션이 볼 수 있는 현상.
격리 수준이 Read UnCommited 이하일 때 나타나는 현상.
Non-Repetable Read
란 하나의 트랜잭션에서 두번의 동일한 조회를 하였을 때, 서로 다른 결과가 조회되는 현상.
격리 수준이 Read Commited 이하일 때 나타나는 현상.
특정 데이터에 대한 수정이 발생하여 나타나는 현상.
Phantom Read
란 하나의 트랜잭션에서 두번의 동일한 조회를 하였을 때, 다른 트랜잭션의 Insert로 인해 없던 데이터가 조회되는 현상.
격리 수준이 Repetable Read 이하일 때 나타나는 현상.
특정 트랜잭션에서 변경된 값은 commit() 후 다른 트랜잭션에게 조회된다.
Oracle과 같은 대부분의 RDB에서 기본적으로 사용되는 격리 수준이다.
commit()후 수정된 데이터를 조회할 수 있기 때문에 Dirty Read는 발생하지 않는다
.
하지만 commit()되기 전 조회시 Undo영역에 있던 데이터를 읽어오고, commit()후 조회시 수정된 데이터를 읽어오기 때문에 동일한 조회이지만 서로 다른 데이터를 조회해 오기 때문에 Non-Repetable Read가 발생
하고 다른 트랜잭션의 Insert작업으로 인해 Phantom Read가 발생
한다.
Read Commited의 Non-Repetable Read
참조 : https://nesoy.github.io/articles/2019-05/Database-Transaction-isolation
트랜잭션마다 ID를 부여하고 트랜잭션 ID보다 작은 트랜잭션을 가지고 commit()된 데이터만 조회된다.
즉, 트랜잭션이 시작되기 전에 커밋된 데이터만 조회한다.
MySQL이 사용한다.
변경되는 데이터는 Undo영역에 트랜잭션 ID와 함께 백업하고 실제 레코드 값을 변경한다.
MVCC(Multi Version Concurrency Control)
이라고 부른다.Repeatable Read
참조 : https://nesoy.github.io/articles/2019-05/Database-Transaction-isolation
Repetable Read 격리 수준에서는 Non-Repetable Read 부정합이 발생하지 않는다.
하지만 Update 부정합
과 Phantom Read가 발생
한다.
Update 부정합
A트랜잭션이 수행중일 때 B트랜잭션이 수행하여 데이터를 수정했다면 그 이후 A트랜잭션이 수정된 데이터를 수정할 때 수정이 되지 않는 현상.
START TRANSACTION; -- transaction id : 1
SELECT * FROM Member WHERE name='junyoung';
START TRANSACTION; -- transaction id : 2
SELECT * FROM Member WHERE name = 'junyoung';
UPDATE Member SET name = 'joont' WHERE name = 'junyoung';
COMMIT;
UPDATE Member SET name = 'zion.t' WHERE name = 'junyoung'; -- 0 row(s) affected
COMMIT;
해당 결과는 name = 'joont'
이다.
이유는 Repeatable Read의 특징에서 데이터가 수정될 때 이전 데이터는 Undo영역에 저장된다고 했다.
2번 트랜잭션이 이름을 junyoung에서 joont로 변경 후, 1번 트랜잭션이 junyoung을 수정하기 위해 조회할 때 junyoung은 레코드 영역이 아닌 Undo영역에 존재하게 된다.
Repeatable Read에서 Update시 쓰기 잠금이 발생하는데, Undo영역의 데이터는 쓰기 잠금을 걸 수가 없다.
그렇기 때문에 Update를 하기 위한 조건을 레코드 영역에서 찾게되는데 당연히 name = 'junyoung'
이라는 조건의 데이터는 레코드 영역에 존재하지 않는다. 고로 0개의 데이터를 출력하게 되고 아무 변경도 일어나지 않게 된다.
그리고 결과는 2번 트랜잭션에서 수정된 joont가 되게 된다.
다른 트랜잭션에서 Insert시 이전 조회에서 나오지 않았던 새로운 데이터가 이후 조회에서 조회되는 현상.
참조 : https://zzang9ha.tistory.com/381#2-3-repeatable-read
Repeatable Read에서는 해당 트랜잭션 이전에 커밋된 데이터만 읽어오기 때문에 Phantom Read가 어떻게 발생할 수 있지? 라는 생각을 했었다.
이를 이해하기 위해서는 Update쿼리시 발생하는 쓰기 잠금
에 대해서 이해를 해야한다.
https://suhwan.dev/2019/06/09/transaction-isolation-level-and-lock/
위 그림에서 첫번째 SELECT FOR UPDATE 쿼리에서는 1개의 결과를 받았지만, 두번째 SELECT FOR UPDATE 쿼리에서는 다른 트랜잭션에 의한 Insert로 2개의 결과를 받게 된다.
SELECT FOR UPDATE 쿼리는 Select한 row에 쓰기 잠금을 걸게 되는데, 쓰기 잠금은 Undo영역의 데이터에는 적용되지 않는다. 그렇기 때문에 실제 레코드 영역에 있는 데이터를 가져오기 때문에 두번째 SELECT FOR UPDATE시 row가 추가된 2개의 결과를 받게 되는 것이다.
하나의 예시로 https://jyeonth.tistory.com/32 님께서 작성하신 글에서 MySQL에서의 결과를 보자면
빨간색은 Tx1
, 초록색을 Tx2
트랜잭션이, count=0 이라고 할때
Tx1
실행Tx2
실행Tx1
에서 id=2인 row의 count+1로 UPDATETx2
에서 id=2인 row의 count+1로 UPDATETx1
에서 id=2인 row에 대해 쓰기 잠금을 했기 때문에 Tx2
의 UPDATE쿼리는 락이 걸리게 된다.Tx1
commitTx1
가 commit됨으로써 id=2인 row에 대한 락이 풀리면서 4번에서의 Tx2
UPDATE쿼리가 실행된다.Tx1
에서 id=2 row를 SELECT시 count = 1로 +1 수정된것으로 확인된다.Tx2
에서 id=2 row를 SELECT시 count = 2로 +2 수정된것으로 확인된다.눈여겨 봐야할 것은 7번 Tx2
에서 SELECT의 결과이다.
MySQL은 Repeatable Read라 Tx2
가 실행되기 전에는 분명 count=0이었을텐데, Tx2
의 UPDATE로 count=1이 아닌 count=2로 수정이 되어있다.
이유는 위에서 설명했듯이 UPDATE의 쓰기 잠금의 영향을 받은 것이다.
Tx2
에서 UPDATE시 id=2의 count=0이다. 하지만 이는 Tx1
의 UPDATE로 인해 Undo영역에 있는 count=0이고 레코드 영역의 count는 1이다.
그렇기 때문에 Tx2
에서 UPDATE는 id=2의 Undo영역의 count=0인 row가 아닌 레코드 영역의 count=1를 가져와 +1을 업데이트했기 때문에 결과는 count=2가 되는 것이 맞다.
기본적으로 가장 순수한 Select 작업에는 잠금을 걸지않고 동작하지만, Serializable 격리 수준에서는 읽기 작업에서도 공유 잠금
을 설정하여 동시에 다른 트랜잭션이 조회중인 레코드를 변경할 수 없게 된다.
가장 단순하고 가장 엄격한 격리기준.
일관성이 가장 높고 동시성이 가장 낮은 격리기준.
https://zzang9ha.tistory.com/381#2-3-repeatable-read
https://suhwan.dev/2019/06/09/transaction-isolation-level-and-lock/
https://jyeonth.tistory.com/32
https://nesoy.github.io/articles/2019-05/Database-Transaction-isolation