반응형
데이터베이스 트랜잭션 격리 수준 (Transaction Isolation Levels)
데이터베이스 트랜잭션의 격리 수준은 여러 개의 트랜잭션이 동시에 실행될 때, 한 트랜잭션이 다른 트랜잭션의 변경 사항을 어느 정도까지 '볼 수 있는지'를 정의하는 기준입니다. 이는 데이터의 일관성과 무결성을 보장하면서도, 시스템의 동시성(처리량)을 최적화하기 위한 중요한 균형점입니다.
SQL 표준은 4가지 주요 격리 수준을 정의하고 있으며, 각 격리 수준은 특정 동시성 문제(Anomaly)를 방지하는 정도가 다릅니다.
트랜잭션의 4가지 속성 (ACID) 복습
격리 수준을 이해하기 전에, 트랜잭션의 ACID 속성 중 'Isolation'이 무엇인지 다시 상기하는 것이 좋습니다.
- A - Atomicity (원자성): 트랜잭션 내의 모든 작업은 전부 실행되거나, 아니면 전부 실행되지 않아야 합니다. (All or nothing)
- C - Consistency (일관성): 트랜잭션이 성공적으로 완료되면, 데이터베이스는 언제나 일관된 상태를 유지해야 합니다.
- I - Isolation (격리성): 여러 트랜잭션이 동시에 실행될 때, 각 트랜잭션은 마치 다른 트랜잭션이 없는 것처럼 독립적으로 실행되어야 합니다. (이것이 격리 수준의 핵심)
- D - Durability (영속성): 트랜잭션이 성공적으로 커밋되면, 그 변경 사항은 영구적으로 데이터베이스에 저장되어야 합니다.
동시성 문제 (Concurrency Anomalies)의 3가지 유형
격리 수준은 다음 세 가지 주요 동시성 문제를 방지하는 것을 목표로 합니다. 낮은 격리 수준에서는 이러한 문제들이 발생할 수 있습니다.
- Dirty Read (더티 리드 / 미확정 읽기):
- 정의: 한 트랜잭션 A가 다른 트랜잭션 B가 아직 커밋(Commit)하지 않은 변경된 데이터를 읽는 현상입니다.
- 문제점: 만약 트랜잭션 B가 나중에 롤백(Rollback)된다면, 트랜잭션 A는 존재하지 않는(되돌려진) 데이터를 읽게 되어 잘못된 결정을 내릴 수 있습니다. 가장 심각한 동시성 문제입니다.
- 예시:
- (계좌 잔액 1000원)
- 트랜잭션 A 시작
- 트랜잭션 B 시작: 계좌 잔액을 1000원 -> 500원으로 변경 (아직 커밋 안함)
- 트랜잭션 A: 계좌 잔액 조회 -> 500원 (트랜잭션 B의 미확정 변경분을 읽음: Dirty Read 발생!)
- 트랜잭션 B: 롤백 (계좌 잔액 다시 1000원)
- 트랜잭션 A: 500원인 줄 알고 어떤 계산을 계속 진행 -> 잘못된 결과 도출.
- Non-Repeatable Read (반복 불가능 읽기):
- 정의: 한 트랜잭션 A가 동일한 SELECT 쿼리를 두 번 실행했을 때, 그 사이에 다른 트랜잭션 B가 해당 데이터를 UPDATE하고 커밋하여 두 번의 SELECT 결과가 달라지는 현상입니다.
- 문제점: 트랜잭션 A는 동일한 데이터를 두 번 읽었는데 결과가 달라져서 혼란을 겪거나, 비즈니스 로직에 오류를 유발할 수 있습니다. (행의 '값'이 변경됨)
- 예시:
- (상품 가격 1000원)
- 트랜잭션 A 시작
- 트랜잭션 A: 상품 가격 조회 -> 1000원
- 트랜잭션 B 시작: 상품 가격을 1000원 -> 1200원으로 변경하고 커밋
- 트랜잭션 A: 상품 가격 다시 조회 -> 1200원 (Non-Repeatable Read 발생!)
- 트랜잭션 A: 첫 번째 1000원 기준으로 하려던 계산이 꼬임.
- Phantom Read (팬텀 리드 / 유령 읽기):
- 정의: 한 트랜잭션 A가 특정 조건을 가진 행들을 SELECT 쿼리로 두 번 조회했을 때, 그 사이에 다른 트랜잭션 B가 해당 SELECT 조건에 해당하는 새로운 행을 INSERT하고 커밋하여 두 번째 SELECT 결과에 새로운(유령 같은) 행이 나타나는 현상입니다. (행의 '개수'가 변경됨)
- 문제점: 집계 함수(COUNT, SUM)를 사용하는 쿼리에서 특히 문제가 될 수 있으며, 데이터셋의 일관성을 기대하는 로직에 혼란을 줍니다.
- 예시:
- (학생 목록: 철수, 영희)
- 트랜잭션 A 시작
- 트랜잭션 A: '남학생' 목록 조회 -> '철수'
- 트랜잭션 B 시작: 새로운 남학생 '민수'를 INSERT하고 커밋
- 트랜잭션 A: '남학생' 목록 다시 조회 -> '철수', '민수' (Phantom Read 발생!)
- 트랜잭션 A: '남학생 수'를 세는 로직이 두 번의 조회에서 다른 결과를 얻음.
SQL 표준의 4가지 격리 수준
이제 각 격리 수준이 어떤 동시성 문제를 방지하는지 알아보겠습니다. (아래로 갈수록 격리 수준이 높아지고, 동시성 문제는 줄어들지만, 성능은 저하될 수 있습니다.)
격리 수준 Dirty Read 방지 Non-Repeatable Read 방지 Phantom Read 방지 설명
| 1. READ UNCOMMITTED | ❌ | ❌ | ❌ | 가장 낮은 격리 수준. 다른 트랜잭션이 아직 커밋하지 않은 데이터를 읽을 수 있습니다. 모든 동시성 문제 발생 가능. 성능은 가장 빠르지만, 데이터 일관성 최악. 거의 사용되지 않음. |
| 2. READ COMMITTED | ✅ | ❌ | ❌ | 다른 트랜잭션이 커밋한 데이터만 읽을 수 있습니다. Dirty Read는 방지하지만, Non-Repeatable Read와 Phantom Read는 발생 가능. 대부분의 애플리케이션에서 가장 많이 사용되는 기본 격리 수준 중 하나 (Oracle, PostgreSQL 기본). |
| 3. REPEATABLE READ | ✅ | ✅ | ❌(PostgreSQL) / ✅(MySQL InnoDB) | 한 트랜잭션 내에서 동일한 데이터를 여러 번 읽어도 항상 같은 값을 보장합니다. Dirty Read와 Non-Repeatable Read는 방지. 하지만 Phantom Read는 여전히 발생할 수 있습니다. (단, MySQL InnoDB는 Next-Key Lock으로 Phantom Read도 방지하려고 노력함) |
| 4. SERIALIZABLE | ✅ | ✅ | ✅ | 가장 높은 격리 수준. 모든 동시성 문제를 방지하여, 마치 트랜잭션들이 순차적으로 실행된 것처럼 완벽하게 격리됩니다. 데이터 일관성은 최고지만, 동시성이 크게 저하되어 성능이 가장 느립니다. 복잡한 분석이나 금융 거래처럼 엄격한 데이터 일관성이 요구될 때 사용. |
각 격리 수준의 자세한 설명
1. READ UNCOMMITTED (커밋되지 않은 읽기)
- 설명: 가장 낮은 격리 수준입니다. 한 트랜잭션이 다른 트랜잭션이 아직 커밋하지 않은 데이터를 읽는 것을 허용합니다. (즉, Dirty Read가 발생할 수 있습니다.)
- 장점: Lock을 거의 걸지 않으므로 동시성이 매우 높고 성능이 가장 빠릅니다.
- 단점: 데이터의 신뢰성이 매우 낮아 실제 애플리케이션에서는 거의 사용되지 않습니다. (로그 분석 등 데이터의 일관성이 중요하지 않은 경우 가끔 사용될 수 있음)
2. READ COMMITTED (커밋된 읽기)
- 설명: 다른 트랜잭션이 커밋한 데이터만 읽을 수 있습니다. 따라서 Dirty Read는 발생하지 않습니다. 그러나 한 트랜잭션 내에서 같은 쿼리를 두 번 실행했을 때, 그 사이에 다른 트랜잭션이 커밋되면 결과가 달라질 수 있습니다. (Non-Repeatable Read, Phantom Read 발생 가능)
- 장점: Dirty Read를 방지하면서도 REPEATABLE READ나 SERIALIZABLE보다 높은 동시성을 제공합니다.
- 단점: Non-Repeatable Read와 Phantom Read가 발생할 수 있어, 동일한 데이터에 대한 반복적인 읽기 일관성이 필요한 경우에는 부적합합니다.
- 사용 예: Oracle과 PostgreSQL의 기본 격리 수준입니다. 대부분의 웹 애플리케이션처럼 빠른 응답과 높은 동시성이 요구되면서도, "최근 커밋된 데이터"를 읽는 것이 중요한 경우에 적합합니다.
3. REPEATABLE READ (반복 가능한 읽기)
- 설명: 한 트랜잭션 내에서 동일한 쿼리를 여러 번 실행했을 때, 항상 같은 결과를 반환함을 보장합니다. 즉, Dirty Read와 Non-Repeatable Read를 방지합니다.
- PostgreSQL: MVCC를 사용하여 트랜잭션이 시작된 시점의 스냅샷을 읽기 때문에, Non-Repeatable Read와 Phantom Read 모두 방지됩니다. (PostgreSQL의 REPEATABLE READ는 SQL 표준의 SERIALIZABLE에 가깝게 동작합니다.)
- MySQL InnoDB: MVCC와 **Next-Key Lock (Gap Lock + Record Lock)**을 사용하여 Non-Repeatable Read와 Phantom Read를 방지합니다. (Gap Lock은 행과 행 사이의 "간격"에 Lock을 걸어 INSERT를 방지합니다.)
- 장점: 데이터의 일관성이 매우 높으면서도, SERIALIZABLE보다는 동시성이 좋습니다.
- 단점: 여전히 Phantom Read가 발생할 수 있습니다 (SQL 표준 기준, MySQL InnoDB는 이를 방지하려 노력함). Lock 사용이 늘어나 READ COMMITTED보다 동시성이 낮아집니다.
- 사용 예: MySQL InnoDB의 기본 격리 수준입니다. 특정 데이터셋에 대한 반복적인 계산이나 분석이 필요한 경우에 사용됩니다.
4. SERIALIZABLE (직렬화 가능)
- 설명: 가장 높은 격리 수준입니다. 모든 동시성 문제를 (Dirty Read, Non-Repeatable Read, Phantom Read) 완벽하게 방지합니다. 마치 모든 트랜잭션이 순차적으로(하나씩) 실행된 것처럼 동작하여, 어떤 트랜잭션도 다른 트랜잭션의 변경 사항에 영향을 받지 않습니다.
- 장점: 데이터 일관성을 완벽하게 보장합니다.
- 단점: Lock 경합이 매우 심해져 동시성이 가장 낮고 성능이 크게 저하됩니다. 복잡한 쿼리가 많거나 트랜잭션 시간이 길어지면 시스템의 처리량이 급격히 감소할 수 있습니다.
- 사용 예: 엄격한 데이터 일관성이 절대적으로 요구되는 금융 거래 시스템, 재고 관리 시스템 등에서 사용될 수 있지만, 성능 때문에 매우 신중하게 적용해야 합니다.
정리하자면:
- 격리 수준이 낮을수록: 동시성은 높아지고, 성능은 좋지만, 데이터 일관성 문제는 증가합니다.
- 격리 수준이 높을수록: 데이터 일관성은 완벽해지지만, 동시성은 낮아지고 성능은 저하됩니다.
애플리케이션의 요구사항(데이터 일관성의 중요도, 예상되는 동시성 트래픽)에 따라 적절한 격리 수준을 선택하는 것이 중요합니다. 대부분의 웹 애플리케이션에서는 READ COMMITTED나 REPEATABLE READ (데이터베이스의 기본 설정 또는 필요에 따라 선택)가 사용됩니다.
반응형
'CS > DataBase' 카테고리의 다른 글
| Postgresql (1) | 2026.03.12 |
|---|---|
| redo log(mysql 8.0) (0) | 2025.09.16 |
| [DB] Prepared Statement란? (0) | 2025.06.10 |
| [DataBase] 인덱스란? (1) | 2025.03.26 |
| [DB] MVCC(다중 버전 동시성 제어) (0) | 2025.03.26 |