트랜잭션 격리 수준
동시에 트랜잭션이 처리될 때, 트랜잭션끼리 얼마나 서로 고립되어 있는지를 나타낸 것이다.
→ 즉, 특정 트랜잭션이 다른 트랜잭션에서 변경한 데이터를 볼 수 있도록 허용할지 말지를 결정하는 것이다.
level이 아래로 내려갈수록 트랜잭션간 고립 정도가 높아지며, 성능이 떨어지는 것이 일반적이다.
동시성 문제
Dirty Read
트랜잭션1에서 A테이블을 SELECT 한 후 트랜잭션2에서 A테이블 내용을 변경하는 상황.
트랜잭션2가 해당 변경사항을 commit 하지도 않았는데, 트랜잭션1에서 다시 A테이블을 SELECT하면 해당 변경사항을 읽어들일 수 있음.
Non-repeatable Read (Inconsistent Read)
트랜잭션1에서 A테이블을 SELECT 한 후 트랜잭션2에서 A테이블 내용을 변경하는 상황.
트랜잭션2가 해당 변경사항을 commit 한 이후에, 트랜잭션1에서 다시 A테이블을 SELECT 하면 해당 변경사항을 읽어들일 수 있음.
Phantom Read
트랜잭션1이 A테이블을 SELECT 한 후 트랜잭션2에서 A테이블에 내용을 추가하는 상황.
level 2: Repeatable Read가 보장된 경우, A테이블에서 SELECT 해왔던 데이터들을 트랜잭션2가 수정하고 commit 해도,
트랜잭션1에서 다시 A테이블을 SELECT 했을 때, 트랜잭션2의 수정내용을 읽어들일 수 없다.
하지만 트랜잭션2가 데이터를 추가한 경우, 트랜잭션1이 다시 A테이블에서 SELECT하게 되면 기존에 SELECT 했던 데이터에서 row가 추 가될 수 있다. (유령 데이터)
격리 레벨
level 0: Read Uncommitted
트랜잭션2의 변경 내용이 commit이나 rollback과 상관없이 트랜잭션1에서 보여진다.
Dirty Read가 발생할 수 있다.
데이터 정합성에 문제가 많아서 RDBMS 표준에서는 격리수준으로 인정하지 않는다.
level 1: Read Committed
트랜잭션2의 변경 내용이 commit 되어야만 트랜잭션1에서 조회할 수 있다.
+) Oracle에서 기본으로 사용하고 있다.
Dirty Read가 발생하지 않는다. 즉, 다른 트랜잭션에서 commit하지 않는 이상 계속 같은 데이터를 SELECT할 수 있다.
→ 이는 언두 영역에 저장된 데이터이다. (아래 '언두 영역' 설명 참고)
정합성 문제가 해결된 것처럼 보이지만, Non-repeatable Read 문제가 발생할 수 있다.
즉, 하나의 트랜잭션에서 똑같은 SELECT을 수행한 경우 항상 같은 결과를 반환해야 한다는 Repeatable Read 정합성에 어긋난다.
만약 은행 시스템에서 돈의 총합을 보여주는 트랜잭션이 있을 때, 총합을 계산하는 SELECT 쿼리가 여러 번 실행되는 도중 다른 트랜잭션 이 commit을 했다면? → 실행될 때마다 다른 결과값을 가져올 수 있다.
언두 영역
언두 영역은 DB에서 데이터를 변경/삭제했을 때 변경되기 전의 데이터를 보관하는 곳이다.
만약 update 쿼리가 실행되면 트랜잭션을 commit하지 않아도 실제 데이터 파일 내용은 바뀐다.
그리고 변경되지 전의 값이 언두 영역에 백업되는 것이다.
이 상태에서 트랜잭션을 커밋하면 현재 상태가 그대로 유지되고, 롤백하면 언두 영역의 데이터를 다시 데이터 파일로 복구한다.
이러한 과정을 DBMS에서는 MVCC(Multi Version Concurrency Control)라고 표현한다.
하나의 레코드에 대해 2개의 버전이 유지되고, 필요에 따라 어느 데이터가 보여지는지 여러 가지 상황에 따라 달라지는 구조이다.
언두 영역은 크게 두 가지 용도로 사용된다.
1. 트랜잭션 롤백 대비용
2. 트랜잭션의 격리 수준을 유지하면서 높은 동시성을 제공
level 2: Repeatable Read
트랜잭션이 시작되기 전에 commit 된 내용에 대해서만 조회할 수 있는 격리수준이다.
+) MySQL에서 기본으로 사용하고 있다.
Non-repeatable Read 문제가 발생하지 앟는다.
MySQL InnoDB의 트랜잭션은 고유한 트랜잭션 번호(순차적 증가)를 가지고 있고,
자신의 트랜잭션 번호보다 낮은 트랜잭션 번호에서 변경된(커밋된) 것만 보게 된다.
+) Repeatable Read 격리수준은 트랜잭션이 시작된 시점의 데이터를 일관되게 보여주는 것을 보장해야 하기 때문에, 계속 멀티 버전을 관리해야 한다.
그러나 실제로 성능에 영향을 미칠 정도는 아니어서, Read Committed와의 성능차이는 거의 없다고 한다.
Repeatable Read로 드디어 동시성 문제를 해결할 수 있나 싶지만, 현재 수준에서도 여러 부정합 케이스들이 있다.
Update 부정합
1. 트랜잭션1이 시작되고, A테이블 내용을 조회한다.
2. 트랜잭션2가 시작되고, A테이블 내용을 변경하고 commit한다.
3. 트랜잭션1이 A테이블 내용을 변경한다 → 아무 일도 일어나지 않는다.
왜 아무 일도 일어나지 않을까?
트랜잭션2에서 데이터를 변경하고 commit을 하면 트랜잭션1의 일관된 데이터 조회를 보장하기 위해 이전 데이터를 언두 영역에 남겨놔야 한다.
이 상황에서 트랜잭션1이 데이터 변경을 시도하면 update의 경우 변경을 수행할 row에 대한 잠금이 필요하다.
그러나 트랜잭션1이 바라보고 있는 데이터는 언두 영역이고, 언두 영역에 있는 데이터는 쓰기 잠금을 걸 수 없다.
→ 따라서 아무 변경도 일어나지 않게 된다.
→ DML 구문은 멀티 버전을 관리하지 않는다.
Phantom Read
트랜잭션1에서 같은 SELECT 쿼리를 두 번 실행했는데, 첫 번째 쿼리에서 없던 유령 레코드가 두 번째 쿼리에서 나타나는 현상을 말한다.
INSERT에서만 발생한다.
level 3: Serializable
가장 단순하고 가장 엄격한 격리수준이다.
이 경우 읽기 작업에도 공유 잠금을 설정하게 되고, 이러면 동시에 다른 트랜잭션에서 해당 레코드를 변경하지 못하게 된다.
이러한 특성 때문에 동시처리 능력이 다른 격리 수준보다 떨어지고, 성능저하가 발생한다.
참고자료
https://www.letmecompile.com/database-transaction-isolation-level/
https://joont92.github.io/db/트랜잭션-격리-수준-isolation-level/
https://devlog-wjdrbs96.tistory.com/368
'database' 카테고리의 다른 글
[Database] MySQL InnoDB lock (2) | 2024.02.05 |
---|---|
PK: UUID vs Auto Increment (0) | 2023.07.19 |
[Postgresql] PostGIS 설치 - MySQL이 아닌 PostgreSQL을 사용하는 이유 (0) | 2023.01.11 |
H2 데이터베이스 설치 (0) | 2021.02.10 |