Foundry
데이터베이스
중급
핵심

Dirty Read / Phantom Read

격리 수준별 발생 가능한 이상 현상

Dirty Read / Phantom Read

트랜잭션 이상 현상 3가지

[Dirty Read]
TX1: UPDATE balance = 0  (커밋 전)
TX2: SELECT balance → 0  ← 커밋 안 된 값 읽음!
TX1: ROLLBACK             ← 원래 1000원인데...
→ TX2는 존재하지 않는 데이터를 읽은 것

[Non-Repeatable Read]
TX1: SELECT age → 25
TX2: UPDATE age = 26, COMMIT
TX1: SELECT age → 26     ← 같은 TX인데 값 변경!
→ 같은 쿼리, 다른 결과

[Phantom Read]
TX1: SELECT COUNT(*) → 10건
TX2: INSERT 1건, COMMIT
TX1: SELECT COUNT(*) → 11건  ← 유령 행 출현!
→ 없던 행이 생김

격리 수준별 방지

격리 수준DirtyNon-RepeatablePhantom
READ UNCOMMITTEDXXX
READ COMMITTEDOXX
REPEATABLE READOOX*
SERIALIZABLEOOO

*MySQL InnoDB는 REPEATABLE READ에서도 Gap Lock으로 Phantom Read 방지

실무 선택 가이드

대부분의 웹 서비스
  → READ COMMITTED (PostgreSQL 기본)
  → REPEATABLE READ (MySQL 기본)

금융/결제 시스템
  → SERIALIZABLE 또는 명시적 락

통계/리포트 조회
  → READ UNCOMMITTED도 가능
     (정확도 < 성능)

면접 포인트

"실무에서 어떤 격리 수준을 쓰나요?"

MySQL: REPEATABLE READ (기본값, Gap Lock으로 Phantom도 방지) PostgreSQL: READ COMMITTED (기본값, MVCC로 성능 확보) 결제처럼 정확성이 중요한 경우 SELECT ... FOR UPDATE 사용

면접에서 이렇게 나옵니다

Q.Dirty Read, Non-Repeatable Read, Phantom Read의 차이를 설명해주세요

무엇이 달라지는지가 다릅니다.

현상상황무엇이 달라지나원인
더티 리드커밋 전 값을 읽는다롤백되면 존재한 적 없는 값을 본 셈커밋 전 노출
반복 불가같은 행을 두 번 읽는다그 행의 값이 다르다다른 트랜잭션의 UPDATE
팬텀같은 조건으로 두 번 조회한다조건에 맞는 행 수가 다르다다른 트랜잭션의 INSERT 나 DELETE

이 구분이 실무에서 중요한 이유는 막는 방법이 다르기 때문입니다. 반복 불가는 그 행을 잠그면 되지만, 팬텀은 아직 없는 행을 잠글 수 없어 범위 자체를 잠가야 합니다.

흔한 실수: 반복 불가와 팬텀을 같은 것으로 설명하는 것. 행 하나냐 결과 집합이냐가 갈림길입니다.

Q.REPEATABLE READ에서 Phantom Read가 발생할 수 있나요?

표준에서는 발생할 수 있고, 구현에 따라 다릅니다.

구현팬텀
표준 정의허용된다
MySQL InnoDB일반 조회는 스냅샷을 보므로 발생하지 않는다. 잠금 조회는 갭 잠금으로 막는다
PostgreSQL스냅샷 격리라 발생하지 않는다

InnoDB 에서 예외가 남는 경우가 있습니다. 스냅샷을 보던 트랜잭션이 중간에 SELECT ... FOR UPDATE 나 UPDATE 를 하면 그 시점의 최신 데이터를 보게 되어, 앞서 본 결과와 달라질 수 있습니다.

흔한 실수: "InnoDB 는 갭 잠금으로 막으니 팬텀이 없다"로 단정하는 것. 갭 잠금은 잠금 조회에서 동작하고, 일반 조회는 스냅샷 덕분에 안 보이는 것이라 원리가 다릅니다. 그리고 갭 잠금은 그만큼 잠금 범위를 넓혀 데드락을 늘리는 대가가 있습니다.

먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.

읽었으면 문제로 확인해보세요

데이터베이스 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.