데이터베이스 동시성 제어(Concurrency Control) 과정에서 트랜잭션 간의 격리성(Isolation)이 완벽하게 보장되지 않을 때 발생하는 대표적인 4가지 이상 현상(Anomaly)이 있습니다.
각 현상의 발생 원리와 구체적인 예시를 시간 흐름(Time)에 따라 정리해 드립니다.
1. 갱신 손실 (Lost Update)
두 개 이상의 트랜잭션이 동시에 같은 데이터를 읽고 수정할 때, 먼저 수정한 결과가 나중에 수정한 결과에 의해 덮어씌워져 무효화되는 현상입니다.
- 원리: 트랜잭션이 데이터를 읽은 후 업데이트를 반영하기 전에, 다른 트랜잭션이 동일한 과거 데이터를 기준으로 업데이트를 수행하여 덮어씁니다.
- 예시: 계좌 잔액이 10,000원인 상황에서 T1이 5,000원을 입금하고, T2가 3,000원을 입금하는 경우 (기대 결과: 18,000원)

2. 비정상 읽기 (Dirty Read)
어떤 트랜잭션이 아직 커밋(Commit)되지 않은 다른 트랜잭션의 데이터를 읽어오는 현상입니다.
- 원리: T1이 데이터를 수정했지만 아직 커밋하지 않은 상태에서 T2가 그 데이터를 읽고 작업을 수행합니다. 만약 T1이 롤백(Rollback)해버리면, T2는 실제 데이터베이스에 존재하지도 않았던 '잘못된(Dirty)' 데이터를 바탕으로 로직을 처리하게 됩니다.
- 예시: 상품 재고가 10개인 상황

3. 반복 불가능 읽기 (Non-Repeatable Read)
한 트랜잭션 내에서 동일한 데이터를 두 번 이상 읽을 때, 그 결괏값이 서로 다르게 나타나는 현상입니다.
- 원리: T1이 데이터를 읽고 아직 종료되지 않은 시점에, T2가 해당 데이터를 **수정(Update)하거나 삭제(Delete)**하고 커밋해버리면, T1이 다시 해당 데이터를 읽었을 때 이전과 다른 값을 보게 됩니다. (행 단위의 일관성 깨짐)
- 예시: 회원 등급 조회 시스템

4. 유령 읽기 (Phantom Read)
한 트랜잭션 내에서 동일한 조건(Where 구문 등)으로 여러 번 조회할 때, 이전에 없던 레코드가 나타나거나 있던 레코드가 사라지는 현상입니다.
- 원리: Non-Repeatable Read가 특정 레코드(행)의 수정/삭제에 의해 발생한다면, Phantom Read는 조건에 맞는 새로운 레코드의 **삽입(Insert)**에 의해 발생합니다.
- 예시: 특정 부서의 직원 목록 조회

💡 격리 수준(Isolation Level)과 이상 현상 방지
위의 이상 현상들을 제어하기 위해 ANSI/ISO SQL 표준에서는 4단계의 트랜잭션 격리 수준을 정의하고 있습니다. 격리 수준이 높아질수록 동시성은 떨어지지만 데이터 정합성은 올라갑니다.
| 격리 수준 (Isolation Level) | Dirty Read | Non-Repeatable Read | Phantom Read | 동시성 | 데이터 정합성 |
| Read Uncommitted (Level 0) | 🔴 발생 | 🔴 발생 | 🔴 발생 | 최고 | 최하 |
| Read Committed (Level 1) | 🟢 방지 | 🔴 발생 | 🔴 발생 | 높음 | 낮음 |
| Repeatable Read (Level 2) | 🟢 방지 | 🟢 방지 | 🔴 발생 | 낮음 | 높음 |
| Serializable (Level 3) | 🟢 방지 | 🟢 방지 | 🟢 방지 | 최하 | 최고 |
1. Read Uncommitted (커밋되지 않은 읽기)
가장 낮은 수준의 격리 수준으로, 각 트랜잭션의 변경 내용이 커밋(Commit)이나 롤백(Rollback) 여부와 상관없이 다른 트랜잭션에게 보여집니다.
- 특징: 데이터베이스의 동시 처리 성능은 가장 높지만, 데이터의 일관성은 가장 낮습니다.
- 발생하는 이상 현상: Dirty Read, Non-Repeatable Read, Phantom Read가 모두 발생합니다.
- 정합성에 문제가 많아 실무에서는 사실상 사용하지 않는 수준입니다.
2. Read Committed (커밋된 읽기)
어떤 트랜잭션이 데이터를 변경하더라도, 커밋이 완료된 데이터만 다른 트랜잭션에서 조회할 수 있는 수준입니다.
- 특징: 커밋 전의 임시 데이터는 언두(Undo) 영역에 백업되며, 다른 트랜잭션은 이 백업된 원본 데이터를 읽습니다.
- 방지되는 현상: Dirty Read를 방지합니다.
- 발생하는 이상 현상: Non-Repeatable Read, Phantom Read는 여전히 발생합니다. 하나의 트랜잭션 내에서 똑같은 SELECT 쿼리를 실행했을 때 다른 결과가 나올 수 있습니다.
- Oracle, PostgreSQL, SQL Server 등 대부분의 상용 RDBMS가 기본(Default)으로 사용하는 격리 수준입니다.
3. Repeatable Read (반복 가능한 읽기)
트랜잭션이 시작되기 전에 커밋된 내용에 대해서만 조회할 수 있는 수준입니다. 자신의 트랜잭션 번호보다 낮은 트랜잭션 번호에서 커밋된 것만 보게 됩니다.
- 특징: 트랜잭션이 지속되는 동안에는 동일한 쿼리를 여러 번 수행해도 항상 동일한 결과를 반환함을 보장합니다.
- 방지되는 현상: Dirty Read, Non-Repeatable Read를 방지합니다.
- 발생하는 이상 현상: 원칙적으로 Phantom Read가 발생할 수 있습니다. (조회 조건에 맞는 새로운 데이터가 삽입되는 것은 막지 못함)
- MySQL(InnoDB 스토리지 엔진)의 기본 격리 수준입니다. 단, InnoDB는 MVCC(다중 버전 동시성 제어)와 넥스트 키 락(Next-Key Lock) 메커니즘을 함께 사용하여 이 격리 수준에서도 Phantom Read 현상까지 방지하는 특징이 있습니다.
4. Serializable (직렬화 가능)
가장 엄격한 격리 수준으로, 트랜잭션들이 동시에 실행되는 것이 아니라 순차적으로 하나씩 실행되는 것처럼 완벽한 읽기 일관성을 제공합니다.
- 특징: 읽기 작업(SELECT)을 할 때도 공유 잠금(Shared Lock)을 획득해야 하며, 다른 트랜잭션은 해당 데이터를 수정하거나 입력할 수 없습니다.
- 방지되는 현상: Dirty Read, Non-Repeatable Read, Phantom Read 등 모든 이상 현상을 방지합니다.
- 실무 적용: 데이터의 완벽한 일관성을 보장하지만 동시 처리 성능이 극단적으로 떨어지기 때문에, 매우 특수한 상황(예: 절대적인 정합성이 필요한 통계 처리 등)이 아니면 사용하지 않습니다.
💡 read comitted와 repeatable read의 차이
두 격리 수준 모두 '커밋된 데이터만 읽는다'는 점은 동일합니다. 이 부분이 데이터베이스 동시성 제어를 이해할 때 가장 헷갈리기 쉬운 핵심 지점입니다.
둘의 결정적인 차이는 "어느 시점까지 커밋된 데이터를 기준으로 읽을 것인가?"에 있습니다.
1. 핵심 차이: 일관성을 보장하는 "기준 시점"
- Read Committed (커밋된 읽기): 쿼리(Query) 단위의 일관성
- 트랜잭션 내부에서 SELECT 쿼리가 실행되는 바로 그 순간까지 커밋된 최신 데이터를 읽어옵니다.
- 따라서 같은 트랜잭션 안에서 여러 번 조회를 하더라도, 그사이에 다른 트랜잭션이 데이터를 수정하고 커밋했다면 조회할 때마다 결과가 달라질 수 있습니다.
- Repeatable Read (반복 가능한 읽기): 트랜잭션(Transaction) 단위의 일관성
- 트랜잭션이 처음 시작된 시점(BEGIN)에 커밋되어 있던 데이터를 스냅샷처럼 사진을 찍어둡니다.
- 이후 트랜잭션이 종료될 때까지 다른 트랜잭션이 아무리 데이터를 수정하고 커밋하더라도, 처음 찍어둔 스냅샷 데이터만 계속 읽습니다. 따라서 조회 결과가 항상 동일함을 보장합니다.
2. 시간 흐름에 따른 비교 (Non-Repeatable Read 발생 여부)
동일한 상황에서 두 격리 수준이 어떻게 다르게 동작하는지 비교해 보겠습니다. 잔액이 10,000원인 계좌를 T1이 두 번 조회하고, 그사이 T2가 금액을 변경하는 상황입니다.
| 시간 | 트랜잭션 1 (T1) | 트랜잭션 2 (T2) | Read Committed의 T1 조회 결과 | Repeatable Read의 T1 조회 결과 |
| t1 | [BEGIN] 첫 번째 잔액 조회 | 10,000원 | 10,000원 | |
| t2 | [BEGIN] 잔액 20,000원으로 수정 | (T1 대기 중) | (T1 대기 중) | |
| t3 | [Commit] | (T1 대기 중) | (T1 대기 중) | |
| t4 | 두 번째 잔액 재조회 | 20,000원 (최신 커밋 읽음) | 10,000원 (처음 스냅샷 읽음) | |
| t5 | [Commit] | 결과가 달라짐 (Non-Repeatable Read) | 결과가 유지됨 |
3. 요약
- Read Committed: "내가 방금 조회 버튼을 누르기 직전까지 확정(Commit)된 남의 데이터를 보여줘."
- Repeatable Read: "내가 이 작업을 처음 시작했을 때 확정(Commit)되어 있던 데이터만 끝날 때까지 똑같이 보여줘."
결과적으로 Read Committed는 한 트랜잭션 내에서 조회 결과가 달라지는 Non-Repeatable Read 이상 현상을 허용하는 반면, Repeatable Read는 이를 완벽하게 방지한다는 것이 가장 큰 차이점입니다.
'DB' 카테고리의 다른 글
| 2PLP (2-Phase Locking Protocol) (0) | 2026.02.15 |
|---|---|
| 낙관적 병행제어(Optimistic Concurrency Control, OCC) (0) | 2026.02.15 |
| [병행제어] 타임스탬프 순서 규약 (Timestamp Ordering Protocol) (0) | 2026.02.15 |
| 인덱스 분포도 (1) | 2026.02.14 |
| 데이터베이스 주요 조인기법 (0) | 2026.02.14 |