1. Transaction 격리 수준 (isolation level)
트랜잭션의 격리 수준이란, 여러 트랜잭션이 동시에 처리될 때 특정 트랜잭션이 다른 트랜잭션에서 변경하거나
조회하는 데이터를 볼 수 있게 허용할지 말지를 결정하는 것이다. - Real MySql 8.0
트랜잭션 격리 수준의 정의는 위와 같습니다.
그렇다면 해당 트랜잭션 격리 수준을 어떠한 기준으로, 무엇을 설정해야 할까요?
결론부터 말하면, 트랜잭션 격리 수준과 관련된 키워드는 '동시성'과 '데이터 정합성'입니다.
따라서 격리 수준을 어떻게 설정하는지에 따라서 동시성과 데이터 정합성이 달라지기 때문에
DB의 트랜잭션 격리 수준을 설정할 때 서비스의 동시성과 데이터 정합성 중 무엇이 중요한지,
얼마나 중요한지를 판단해보고 설정하는 것이 중요할 것입니다!
격리 수준은 다음과 같이 4개가 존재합니다.
- READ UNCOMMITTED
- READ COMMITTED
- REPEATABLE READ
- SERIALIZABLE
READ UNCOMMITTED -> SERIALIZABLE로 갈수록 데이터 정합성이 잘 지켜지고, 동시성이 떨어지게 됩니다.
즉 READ UNCOMMITTED는 가장 동시성이 좋은 대신에 데이터 정합성이 잘 지켜지지 않을 수 있고,
SERIALIZABLE은 가장 데이터 정합성을 잘 지킬 수 있는 대신에 동시성이 좋지 않습니다.
2. Transaction 격리 수준에 따라 발생할 수 있는 데이터 정합성 문제점
Transaction 격리 수준에 따라 발생할 수 있는 데이터 정합성 문제점에는 크게 다음과 같은 3가지가 존재합니다.
- DIRTY READ
- NON-REPEATABLE READ
- PHANTOM READ
이제 해당 3가지 문제 현상에 대해서 MySql을 사용한 예제 기반으로 설명해보겠습니다.
2-1. DIRTY READ
쿼리 실행 Flow는 다음과 같습니다.
1. 트랜잭션 A에서 MEMBER 테이블에 '승하' Member Insert
2. 트랜잭션 B에서 MEMBER SELECT
3. 트랜잭션 A 커밋
해당 Flow에서 데이터 정합성이 지켜진다면,
2번에서 트랜잭션 B의 Member Select가 일어날 때
아직 Member를 Insert한 트랜잭션 A의 커밋이 되지 않았기 때문에
트랜잭션 B의 Member Select의 결과가 기존 결과인 '성하' 1명만 Select가 되어야 합니다.
이때 Dirty Read는 트랜잭션 B에서 Member Select를 했을 때
사진처럼 트랜잭션 A에서 Insert만 되고 커밋되지 않은 결과까지 Select가 되는 것을 의미합니다.
이러한 Dirty Read 현상은 트랜잭션 A가 롤백된다고 했을 때,
트랜잭션 B에서는 Insert된 Member까지 Select되어 최종 결과와 달라질 수 있다는 점에서 문제가 될 수 있습니다.
2-2. NON-REPEATABLE READ (REPEATABLE READ 불가능)
쿼리 실행 Flow는 다음과 같습니다.
1. 트랜잭션 B에서 ACCOUNT 테이블에 account_id가 1인 레코드 조회 : Money 값 1000
2. 트랜잭션 A에서 ACCOUNT 테이블에 account_id가 1인 레코드의 money 2000으로 변경
3. 트랜잭션 A 커밋
4. 트랜잭션 B에서 ACCOUNT 테이블에 account_id가 1인 레코드 조회 : Money 값 2000
간단하게 요약하면,
트랜잭션 A에서 ACCOUNT 테이블의 account_id가 1인 레코드의 money 값을 변경하고
트랜잭션 A가 커밋된 후에 트랜잭션 B에서 해당 레코드를 조회하는 Flow입니다.
이때 트랜잭션 A의 변경 작업 커밋 이전과 이후의 결과를 보면 알 수 있듯이 조회 결과가 1000, 2000으로 다릅니다.
이렇게 SELECT 쿼리를 실행 했을 때 항상 같은 결과를 가져와야 한다는 REPEATABLE READ가 지켜지지 않는 문제가 바로
NON-REPEATABLE READ입니다.
이러한 문제는 다음과 같은 상황에서 치명적일 수 있습니다.
- 위의 상황이 계좌의 잔액 조회라고 가정
- 처음 조회된 계좌의 잔액이 1000원일 때 사용자는 해당 잔액으로 작업을 처리할 것이다. (500원 송금 등)
- 사용자가 500원을 송금하는 작업 과정 중 현재 잔액을 다시 확인하는 과정이 있다고 해보자.
- 이때, 처음 계좌 잔액 조회와 500원 송금 작업 사이에 누군가 사용자에게 1000원을 입금했다면 송금 시 현재 계좌 잔액이 2000원으로 보여질 것이다.
- 이러한 상황은 사용자에게 혼란을 야기할 수 있다.
이렇게 데이터의 정합성이 깨질 수 있습니다.
2-3. PHANTOM READ
쿼리 실행 Flow는 다음과 같습니다.
1. 트랜잭션 B에서 PRODUCT 테이블의 모든 PRODUCT SELECT : Water
2. 트랜잭션 A에서 PRODUCT 테이블에 PRODUCT 'Pizza' 추가
3. 트랜잭션 A 커밋
4. 트랜잭션 B에서 PRODUCT 테이블의 모든 PRODUCT SELECT : Water, Pizza
간단하게 요약하면, 트랜잭션 B에서 PRODUCT 테이블의 모든 PRODUCT를 SELECT 쿼리를 2번 실행하는데
SELECT 쿼리 사이에 다른 트랜잭션인 A에서 PRODUCT 테이블에 PRODUCT를 추가합니다.
이때 트랜잭션 A의 INSERT 이전, 이후로 트랜잭션 B의 SELECT 결과가
'Water'와 'Water', 'Pizza'로 달라지는 것을 확인할 수 있습니다.
이렇듯, 다른 트랜잭션에서 수행한 변경 작업(커밋된 변경 작업)에 의해
이전 조회 레코드에서 존재하지 않았던 레코드가 조회 시 새롭게 추가되어 나타나는 현상을 PHANTOM READ라고 한다.
(조회 1 : Water / 조회 2 : Water, Pizza 레코드 조회 - 기존 조회 시 없었던 Pizza 레코드 새롭게 추가되어 나타남)
이는 NON-REPEATABLE READ와 조회 결과가 다르다는 점이 비슷하지만,
NON-REPEATABLE READ는 이전의 결과 레코드 대상은 같지만 값이 변한 것이고
PHANTOM READ는 이전의 결과 레코드 자체에서 없었던 레코드가 추가로 생성된 것에서 차이가 있다.
3. Transaction 격리 수준의 특징
첫 목차에서 Transaction 격리 수준에 대해 간단하게 살펴보고, 어떤 것이 있는지에 대해 언급만 하고 넘어갔습니다.
두 번째 목차에서는 Transaction 격리 수준에 따라 발생할 수 있는 데이터 정합성 문제점의 종류들을 알아봤는데요.
이제 각 Transaction 격리 레벨의 특징과 어떤 데이터 정합성 문제가 나타날 수 있는지 알아보도록 하겠습니다.
3-1. READ UNCOMMITTED
각 트랜잭션의 변경 내용이 COMMIT, ROLLBACK 여부에 상관 없이 다른 트랜잭션에서 조회되는 격리 수준
READ UNCOMMITTED는 위와 같은 특성을 가지고 있습니다.
따라서, 앞서 살펴봤던 데이터 정합성 문제점들을 다 가지게 됩니다.
- DIRTY READ : 다른 트랜잭션이 커밋되지 않았을 때도 다른 트랜잭션의 쿼리 실행 결과가 조회되므로 발생
- NON-REPEATABLE READ : 항상 똑같은 조회 결과가 아니라 다른 트랜잭션의 결과에 따라 조회 결과가 변하므로 발생
- PHANTOM READ : 다른 트랜잭션의 레코드 추가 작업이 발생하면 없었던 레코드가 조회 시 나타나므로 발생
이렇듯 데이터 정합성 문제점들을 다 가지기 때문에 데이터 정합성 측면에서 가장 좋지 않은 격리 레벨입니다.
하지만, 데이터 정합성을 신경쓰지 않기 때문에 DB Lock이 거의 사용되지 않아서 동시성이 좋아집니다.
3-2. READ COMMITTED
커밋이 완료된 트랜잭션의 변경사항만 다른 트랜잭션에서 조회할 수 있도록 허용하는 격리 수준
READ COMMITTED는 위와 같은 특성을 가지고 있습니다.
따라서, 앞서 살펴봤던 데이터 정합성 문제점들 중 다음과 같은 문제점을 가집니다.
- NON-REPEATABLE READ : 다른 트랜잭션의 커밋 시점 후에 조회 결과가 변할 수 있으므로 발생
- PHANTOM READ : 다른 트랜잭션의 레코드 추가 작업 커밋 이후 시점에 없었던 레코드가 조회 시 나타나므로 발생
READ COMMITTED는 '커밋이 완료된 트랜잭션의 변경사항만 조회'하기 때문에
커밋 되기 이전 결과가 조회되는 DIRTY READ 문제는 해결할 수 있습니다.
하지만, 여전히 NON-REPEATABLE READ와 PHANTOM READ가 발생할 수 있습니다.
기본적으로 가장 많이 사용하는 트랜잭션 격리 수준으로, 대표적으로 오라클 DBMS에서 사용한다고 합니다.
3-3. REPEATABLE READ
트랜잭션이 시작되기 전에 커밋된 내용에 대해서만 조회할 수 있는 격리수준
REPEATABLE READ는 위와 같은 특성을 가지고 있습니다.
따라서, 앞서 살펴봤던 데이터 정합성 문제점들 중 다음과 같은 문제점을 가집니다.
- PHANTOM READ : 다른 트랜잭션의 레코드 추가 작업 커밋 이후 시점에 없었던 레코드가 조회 시 나타나므로 발생
REPEATABLE READ는 '트랜잭션 시작 전 존재하는 레코드 조회'에 관해서는 항상 동일한 결과를 보장합니다.
따라서 다른 트랜잭션의 커밋 시점 후에 조회 결과가 변하지 않으므로, NON-REPEATABLE READ 문제를 해결할 수 있습니다.
하지만, 추가되는 레코드에 대해서는 동일한 조회 결과를 보장하지 않으므로 PHANTOM READ가 발생할 수 있습니다.
MySQL의 InnoDB엔진의 기본 격리 수준이 REPEATABLE READ입니다.
MySQL의 InnoDB엔진을 사용했을 때는 MySQL InnoDB의 특성으로 인해 PHANTOM READ도 해결할 수 있습니다.
(어떠한 특성으로 해결하는지는 이후에 알아보도록 해보겠습니다!)
그래서 기본적으로 MySQL에서 기본 격리 수준으로 REPAEATABLE READ을 설정하면
데이터 정합성 문제들이 모두 해결되기 때문에 MySQL의 기본 격리 수준으로 REPAEATABLE READ을 설정한 것 같습니다.
3-4. SERIALIZABLE
하나의 트랜잭션에서 읽고 쓰는 레코드를 다른 트랜잭션에서 절대 접근할 수 없는 격리 수준
SERIALIZABLE은 위와 같은 특성을 가지고 있습니다.
하나의 트랜잭션에서 작업하는 레코드에 다른 트랜잭션에서 접근할 수 없기 때문에
가장 단순한 격리 수준이면서 동시에 가장 엄격한 격리 수준입니다.
따라서, READ UNCOMMITTED와 반대로 데이터 정합성이 가장 잘 지켜지는 격리 수준이지만
데이터 정합성에 엄격하기 때문에 DB Lock이 많이 걸려서 동시성이 가장 좋지 않습니다.
4. 요약
Transaction의 격리 수준을 결정할 때 신경 쓸 요소는 동시성과 데이터 정합성이었습니다.
다음과 같이 4가지 격리 수준이 존재했습니다.
- READ UNCOMMITTED
- READ COMMITTED
- REPEATABLE READ
- SERIALIZABLE
READ UNCOMMITTED -> SERIALIZABLE로 갈수록 데이터 정합성이 잘 지켜지고, 동시성이 떨어지게 되는 것을 알 수 있었습니다.
각 격리 수준별로 발생할 수 있는 데이터 정합성 문제들이 존재했고, 다음과 같이 정리할 수 있었습니다.
DIRTY READ | NON-REPEATABLE READ | PHANTOM READ | |
READ UNCOMMITTED | O | O | O |
READ COMMITTED | X | O | O |
REPEATABLE READ | X | X | O (InnoDB는 X) |
SERIALIZABLE | X | X | X |
따라서 종합적으로 DB의 트랜잭션 격리 수준을 결정할 때
서비스의 동시성과 데이터 정합성 중 어떤 것이 중요한 서비스인지, 얼마나 중요한 서비스인지
격리 수준별 발생할 문제들을 예상해보고 결정하는 것이 중요할 것 같습니다! 👍🏻
Reference
- Real MySQL 8.0 (백은빈, 이성욱 지음
'DB' 카테고리의 다른 글
[DB] MySQL InnoDB의 인덱스(feat. 클러스터링 인덱스, 세컨더리 인덱스, 인덱스 스캔 종류, 다중 컬럼 인덱스) (1) | 2023.11.13 |
---|---|
[DB] DB 인덱스(Index)란? (1) | 2023.11.12 |
[DB] DB Lock이란? (feat. Lock 종류, 블로킹, 데드락) (0) | 2023.11.08 |
[DB] 트랜잭션이란? (feat. ACID 특성) (2) | 2023.04.18 |
[DB] 친구 테이블 ERD 설계, 친구 목록 조회 기능 구현(Querydsl 셀프 조인) (1) | 2023.02.01 |