CS Fundamentals
트랜잭션 격리 수준에 대해서 설명해주세요
Dirty Read부터 Phantom Read까지 세 가지 동시성 문제와 네 가지 격리 수준의 관계를 인터랙티브 타임라인으로 이해합니다. MVCC가 어떻게 격리를 구현하는지, MySQL과 PostgreSQL은 무엇이 다른지 면접에서 바로 답할 수 있게 정리합니다.
2026-03-21 · 약 13분 읽기
Q. "트랜잭션 격리 수준(Isolation Level)이 무엇인지 설명하고, 각 수준에서 발생하거나 방지되는 동시성 문제를 말씀해주세요."
예상 꼬리질문
답변 가이드
트랜잭션 격리 수준은 동시에 실행되는 여러 트랜잭션이 서로의 작업을 어느 정도까지 볼 수 있는지를 결정하는 규칙입니다. SQL 표준은 READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE 네 가지를 정의하며, 격리 수준이 높을수록 Dirty Read, Non-Repeatable Read, Phantom Read 세 가지 이상 현상을 더 많이 차단합니다.
핵심 구현 메커니즘은 MVCC(다중 버전 동시성 제어)입니다. 데이터를 수정할 때 기존 버전을 덮어쓰지 않고 새 버전을 생성하여, 각 트랜잭션이 자신의 시작 시점에 맞는 버전을 읽게 합니다. READ COMMITTED는 쿼리마다 새 스냅샷을 생성하고, REPEATABLE READ는 트랜잭션 시작 시 스냅샷을 고정합니다. 덕분에 읽기와 쓰기가 서로 블로킹하지 않아 높은 동시성을 확보합니다.
실무에서는 대부분 DB 기본값으로 충분합니다(MySQL: REPEATABLE READ, PostgreSQL: READ COMMITTED). 금융 정산이나 좌석 예약처럼 절대적 정합성이 필요한 특정 트랜잭션에만 격리 수준을 높이는 것이 성능과 정합성의 균형을 맞추는 방법입니다. 격리 수준이 높을수록 락 경합과 데드락 확률도 함께 높아지기 때문입니다.
핵심 요약 (TL;DR)
- - 격리 수준은 동시 트랜잭션 간 "얼마나 서로를 차단할지"를 결정하며, 높을수록 정합성은 강해지고 성능은 낮아진다
- - Dirty Read(미커밋 읽기) → Non-Repeatable Read(같은 행 재조회 값 변경) → Phantom Read(범위 조회 행 수 변경) 순서로 격리 수준이 높아지며 차단된다
- - MVCC는 데이터의 여러 버전을 유지하여 읽기와 쓰기가 서로 블로킹하지 않게 해주는 핵심 메커니즘이다
- - MySQL InnoDB의 REPEATABLE READ는 Gap Lock으로 Phantom Read까지 방지하는 SQL 표준 이상의 보장을 제공한다
- - PostgreSQL의 SERIALIZABLE은 락 기반이 아닌 SSI(Serializable Snapshot Isolation) 방식으로 더 높은 동시성을 유지한다
면접관이 격리 수준을 물을 때 듣고 싶은 건 단순한 암기가 아니라 "왜 이 수준을 선택했는가"입니다. Dirty Read와 Phantom Read의 차이, MVCC가 어떻게 격리를 구현하는지, MySQL과 PostgreSQL이 왜 기본값이 다른지 — 이 아티클에서 인터랙티브 타임라인으로 직접 체험하며 완전히 이해해 봅시다.
1. 왜 격리 수준이 필요한가 — 세 가지 동시성 문제
꼬리질문: "Dirty Read, Non-Repeatable Read, Phantom Read의 차이점은 무엇인가요?"
격리(Isolation)가 없으면 동시에 실행되는 트랜잭션이 서로의 작업을 방해합니다. 발생 가능한 이상 현상은 세 가지입니다. 더티 리드(Dirty Read)는 커밋되지 않은 데이터를 읽는 것, 반복 불가능 읽기(Non-Repeatable Read)는 같은 행을 두 번 읽었을 때 값이 달라지는 것, 팬텀 리드(Phantom Read)는 같은 조건의 범위 조회에서 행의 수가 달라지는 것입니다.
세 문제는 원인이 미묘하게 다릅니다. Dirty Read는 커밋 전 데이터를 읽는 것이고, Non-Repeatable Read와 Phantom Read는 커밋 후 데이터가 변경되는 것입니다. Non-Repeatable Read는 기존 행의 값이 바뀌고, Phantom Read는 행의 수(집합)가 바뀝니다.
커밋되지 않은 데이터를 다른 트랜잭션이 읽는 현상
이상 현상 비교
| 변경 대상 | 커밋 전 읽기 | 커밋 후 변경 |
|---|---|---|
| 행의 값 | Dirty Read ❌ | Non-Repeatable ⚠ |
| 행의 수 | — | Phantom Read ⚠ |
2. 네 가지 격리 수준 — 무엇을 방지하는가
꼬리질문: "격리 수준을 높이면 무조건 좋은 것 아닌가요?"
SQL 표준은 네 가지 격리 수준을 정의합니다. READ UNCOMMITTED(레벨 0)는 커밋되지 않은 데이터까지 읽을 수 있어 가장 빠르지만 세 가지 이상 현상이 모두 발생합니다. READ COMMITTED(레벨 1)는 Dirty Read를 방지하며 대부분의 DB(PostgreSQL, Oracle, SQL Server)의 기본값입니다. REPEATABLE READ(레벨 2)는 트랜잭션 시작 시 스냅샷을 고정해 Non-Repeatable Read까지 방지하며 MySQL InnoDB의 기본값입니다. SERIALIZABLE(레벨 3)은 모든 이상 현상을 방지하지만 락 경합이 가장 심합니다.
격리 수준이 높아질수록 더 많은 이상 현상을 차단합니다. 아래 매트릭스에서 각 격리 수준을 클릭해 어떤 이상 현상이 방지·허용되는지, 그리고 DB별 기본값이 무엇인지 확인해 보세요. MySQL InnoDB의 REPEATABLE READ는 SELECT ... FOR UPDATE 같은 잠금 읽기에서 Gap Lock을 적용해 SQL 표준 이상의 Phantom Read 방지를 제공합니다.
| 격리 수준 | 더티 리드 (Dirty Read) | 반복 불가 읽기 (Non-Repeatable) | 팬텀 리드 (Phantom) | 성능(처리량) | |
|---|---|---|---|---|---|
레벨 0 READ UNCOMMITTED | 발생 | 발생 | 발생 | ||
레벨 1 READ COMMITTED | 방지 | 발생 | 발생 | ||
레벨 2 REPEATABLE READ | 방지 | 방지 | 부분MySQL: Gap Lock | ||
레벨 3 SERIALIZABLE | 방지 | 방지 | 방지 |
★ MySQL InnoDB의 REPEATABLE READ는 Gap Lock으로 Phantom Read를 방지합니다 (SQL 표준 이상의 보장)
3. MVCC — 격리 수준을 구현하는 핵심 엔진
꼬리질문: "MVCC(Multi-Version Concurrency Control)가 무엇이고, 격리 수준과 어떤 관계인가요?"
다중 버전 동시성 제어(MVCC, Multi-Version Concurrency Control)는 현대 RDBMS가 격리를 구현하는 핵심 메커니즘입니다. 데이터를 수정할 때 기존 버전을 덮어쓰지 않고 새 버전을 생성하고, 각 트랜잭션은 자신의 시작 시점에 맞는 버전을 읽습니다. 덕분에 읽기(SELECT)와 쓰기(UPDATE)가 서로 블로킹하지 않아 높은 동시성이 가능합니다.
READ COMMITTED는 쿼리를 실행할 때마다 새 스냅샷을 생성하고(Statement-level),REPEATABLE READ는 트랜잭션 시작 시 스냅샷을 한 번 고정해 트랜잭션이 끝날 때까지 유지합니다(Transaction-level). MySQL InnoDB는 Undo Log로, PostgreSQL은 행의 xmin/xmax로 버전을 관리하며, 아래 시뮬레이터에서 각 수준이 어떤 버전을 읽는지 확인해 보세요.
① 1st SELECT
→ V1: 100만원
T2 커밋 전 → v1(100만원) 읽음
T2: v2(80만), v3(50만) 순차 커밋
② 2nd SELECT (T2 커밋 후)
→ V3: 50만원
T2 커밋 후 새 스냅샷 → v3(50만원) 읽음
⚡ 읽기 ≠ 쓰기 블로킹 없음
SELECT은 UPDATE를 기다리지 않는다 — MVCC의 핵심
4. MySQL vs PostgreSQL — 같은 표준, 다른 구현
꼬리질문: "MySQL InnoDB의 기본 격리 수준이 REPEATABLE READ인데, PostgreSQL은 왜 READ COMMITTED를 기본으로 쓰나요?"
MySQL InnoDB와 PostgreSQL은 같은 SQL 표준을 구현하지만 기본 격리 수준과 내부 동작이 다릅니다. 가장 큰 차이는 기본 격리 수준(MySQL: REPEATABLE READ, PostgreSQL: READ COMMITTED)과 SERIALIZABLE 구현 방식(MySQL: 락 기반, PostgreSQL: SSI)입니다.
PostgreSQL의 SSI(Serializable Snapshot Isolation)는 락 기반이 아닌 스냅샷 기반으로 SERIALIZABLE을 구현합니다. 읽기-쓰기 충돌을 감지하여 충돌 트랜잭션을 롤백하는 방식으로, MySQL의 락 기반 SERIALIZABLE보다 동시성이 높습니다. 반면 MySQL InnoDB의 REPEATABLE READ는 Gap Lock 덕분에 PostgreSQL REPEATABLE READ보다 강한 Phantom Read 방지를 제공합니다.
REPEATABLE READ
READ COMMITTED
Undo Log 기반 (행 외부 저장)
Tuple Versioning — xmin/xmax 필드 (행 내부)
Gap Lock + Next-Key Lock으로 방지 ✅
직렬화 스냅샷 격리(SSI) 방식으로 방지 ✅
락 기반 — 모든 SELECT에 S-Lock 자동 부여
직렬화 스냅샷 격리(SSI) — 스냅샷 기반 읽기·쓰기 충돌 감지 후 롤백
Purge Thread — 비동기 백그라운드 정리
VACUUM — 명시적 실행 또는 AutoVacuum
💡 Spring에서 격리 수준 지정
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void criticalOperation() {
// 트랜잭션 내 일관성 보장
}5. 격리 수준 선택 기준 — 성능과 정합성의 트레이드오프
꼬리질문: "실무에서 격리 수준을 어떻게 선택하시겠습니까?"
격리 수준을 높이면 데이터 정합성은 강해지지만, 처리량(TPS)은 낮아지고 락 경합과 데드락 확률이 높아집니다. 실무에서는 전역으로 높은 격리 수준을 설정하기보다, 강한 정합성이 필요한 특정 트랜잭션에만 높은 격리 수준을 적용하는 것이 효율적입니다.
어떤 격리 수준을 선택할지는 시스템 유형에 따라 다릅니다. SNS·게시판처럼 약간의 정합성 오차가 허용되는 시스템은 DB 기본값으로 충분하고, 금융·결제·재고 관리처럼 절대적 정합성이 필요한 시스템은 높은 격리 수준이 필요합니다.
| 격리 수준 | 정합성 ↑ 높을수록 좋음 | 처리량 ↑ 높을수록 좋음 | 데드락 위험 ↑ 높을수록 나쁨 | 락 경합 ↑ 높을수록 나쁨 |
|---|---|---|---|---|
| READ UNCOMMITTED | 20% | 100% | 0% | 0% |
| READ COMMITTED | 50% | 80% | 20% | 20% |
| REPEATABLE READ | 75% | 55% | 50% | 55% |
| SERIALIZABLE | 100% | 30% | 80% | 85% |
시스템 유형별 권장 격리 수준
SNS · 게시판 · 일반 CRUD
READ COMMITTED
DB 기본값으로 충분
잔액 계산 · 재고 관리
REPEATABLE READ
트랜잭션 내 일관성 필요
금융 정산 · 좌석 예약
SERIALIZABLE
절대적 정합성 요구
// 특정 메서드에만 높은 격리 수준 적용
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void settle() {
// 금융 정산 등 높은 정합성이 필요한 작업
}자주 발생하는 실무 문제
격리 수준별로 실무에서 마주치는 대표적인 동시성 버그와 해결책입니다. 항목을 클릭해 상세 내용을 확인하세요.
면접 체크리스트
이 항목들을 모두 설명할 수 있다면 면접 준비 완료입니다.
- - Dirty Read: 미커밋 데이터 읽기 — READ COMMITTED 이상에서 방지
- - Non-Repeatable Read: 같은 행 재조회 시 값 변경 — REPEATABLE READ 이상에서 방지
- - Phantom Read: 범위 조회 시 행 수 변경 — SERIALIZABLE에서 방지 (MySQL InnoDB는 REPEATABLE READ에서도 Gap Lock으로 방지)
- - MVCC: 데이터 버전 체인으로 읽기·쓰기 블로킹 없이 격리를 구현하는 핵심 메커니즘
- - 실무 원칙: DB 기본값(MySQL: REPEATABLE READ, PostgreSQL: READ COMMITTED)으로 충분한 경우가 많고, 강한 정합성이 필요한 특정 트랜잭션에만 높은 격리 수준 적용
- - 더 알아볼 주제: 비관적 락 vs 낙관적 락, 분산 트랜잭션과 Saga 패턴, PostgreSQL SSI 내부 동작
커밋 전 데이터를 읽었다면?
T1: UPDATE accounts SET balance = 0 WHERE id = 1; -- 아직 COMMIT 안 함 T2: SELECT balance FROM accounts WHERE id = 1; -- 0원 읽음 T1: ROLLBACK;
T2가 0원을 읽은 이 현상은 무엇이며, 어느 격리 수준부터 방지되나요?
참고 자료
- Vlad Mihalcea — 트랜잭션 격리 수준 입문 (JPA/Spring 실무 예제) — Dirty Read·Non-Repeatable Read·Phantom Read를 JPA 코드 예제로 재현하며 격리 수준을 직접 체험할 수 있는 초보자 친화 가이드
- Baeldung — MySQL 트랜잭션 격리 수준 실습 — Spring Boot + MySQL 환경에서 각 격리 수준의 동작 차이를 단계별 코드로 비교하는 튜토리얼
- Use The Index, Luke — Transaction Isolation — 격리 수준의 실무적 의미를 인덱스·잠금과 연결해 풀어 설명하는 독립 가이드
- Martin Kleppmann — Hermitage: Testing the "I" in ACID — MySQL·PostgreSQL·Oracle 등 실제 DB의 격리 수준 동작을 실험으로 검증한 심화 분석
- Baeldung — Spring @Transactional Propagation & Isolation 완벽 가이드 — @Transactional의 isolation 속성을 propagation과 함께 코드 예제로 설명하는 Spring 실무 튜토리얼
의견을 들려주세요
서비스 개선에 큰 도움이 됩니다. 익명으로 자유롭게 남겨주세요.