Skip to Content

9장. 유령과 오염 — 동시성 괴현상

만화로 보는 요약 — 먼저 읽어보세요

만화로 보는 요약 — 먼저 읽어보세요

격리를 낮추면 동시성 유령이 새어 나온다 → 더티 리드는 커밋 전 값을 보고 반복불가 읽기는 같은 행 값이 바뀐다 → 팬텀 읽기는 조건 결과 집합이 바뀐다.

면접 실전 질문: ① Dirty Read와 rollback의 관계 ② Non-repeatable Read와 Phantom Read의 차이 ③ SQL 표준이 세 유령을 기준으로 삼은 이유


배경 — 격리를 풀면 무엇이 새어 나오나

7장에서 약속했죠. 완벽한 격리는 느려서, 현실의 DB는 격리를 단계로 풀어 속도를 얻는다고요. 그런데 격리를 풀 때마다 뭔가가 새어 나옵니다. 동시에 돌아가는 트랜잭션들이 서로를 훔쳐보거나 밟으면서 생기는, 기묘한 현상들이요.

제3부는 이 현상들과의 싸움입니다. 적을 이기려면 먼저 적의 이름을 알아야죠. 이 장에서는 동시성이 부르는 세 유령에게 이름을 붙입니다. 이름을 알고 나면, 10장에서 “어느 유령을 막고 어느 유령을 허용할지”를 고를 수 있게 돼요.

스토리 — 데이터베이스라는 유령의 집

두 개의 트랜잭션을 각각 세션 A, 세션 B라고 부를게요. 이 둘이 같은 데이터를 동시에 건드릴 때 나타나는 세 가지 유령을 하나씩 만나봅시다.

유령 ①: Dirty Read (오염된 읽기)

A가 아직 커밋하지 않은 변경을, B가 훔쳐봅니다.

[A] UPDATE 상품 가격 = 5000 → 8000 (아직 COMMIT 안 함) [B] SELECT 상품 가격 → 8000 ← A의 미완성 값을 읽음! [A] ROLLBACK (역시 취소) [B] ...방금 읽은 8000은 세상에 존재한 적 없는 값

B는 한 번도 확정된 적 없는 유령 값을 읽었어요. A가 롤백하면 8000은 사라지는데, B는 이미 그걸 믿고 뭔가를 해버렸죠. 커밋 안 된 남의 데이터를 읽는 것 — 이게 더티 리드입니다. 셋 중 가장 위험해서, 대부분의 DB가 기본적으로 막습니다.

유령 ②: Non-repeatable Read (반복 불가능한 읽기)

B가 같은 행을 두 번 읽었는데, 값이 달라집니다.

[B] SELECT 잔고 WHERE id=42 → 10000 [A] UPDATE 잔고 = 5000 WHERE id=42; COMMIT (이번엔 커밋됨) [B] SELECT 잔고 WHERE id=42 → 5000 ← 같은 걸 읽었는데 값이 바뀜!

B는 자기 트랜잭션 안에서 아무것도 안 했는데, 같은 질문의 답이 중간에 바뀌었어요. 방금 전 10000이 지금은 5000. 같은 읽기를 반복할 수 없다는 뜻에서 반복 불가능한 읽기입니다. 더티 리드와 달리, 이번엔 A가 제대로 커밋한 값이라는 게 포인트예요.

유령 ③: Phantom Read (유령 읽기)

B가 같은 조건으로 두 번 조회했는데, 행의 개수가 달라집니다.

[B] SELECT COUNT(*) 주문 WHERE 금액 >= 10000 → 3건 [A] INSERT INTO 주문 (금액) VALUES (50000); COMMIT [B] SELECT COUNT(*) 주문 WHERE 금액 >= 10000 → 4건 ← 없던 행이 유령처럼 나타남!

②가 “이미 있던 행의 이 바뀌는” 것이라면, ③은 “조건에 맞는 행 자체가 생기거나 사라지는” 겁니다. 방금 없던 4번째 주문이 유령처럼 튀어나왔죠. 그래서 팬텀(유령) 리드예요.

핵심 — 세 유령의 정체를 한눈에

셋을 나란히 놓으면 차이가 명확해집니다.

유령무엇을 읽나핵심
Dirty Read커밋 안 된 값존재한 적 없는 유령 값을 읽음
Non-repeatable Read커밋된 값, 이미 읽은 그 행그 행을 다시 읽으니 이 달라짐
Phantom Read커밋된 값, 같은 조건조건에 맞는 행 집합이 달라짐

세 유령을 가르는 축은 두 개예요. (1) 커밋 안 된 걸 보나(더티) vs 커밋된 걸 보나 (나머지 둘), 그리고 (2) 내가 이미 손에 쥔 특정 행의 값이 변하나(반복불가) vs 조건에 맞는 행들의 집합이 변하나(팬텀).

이 세 유령은 아무나 지어낸 게 아닙니다. SQL 표준(4장의 SQL-92)이 격리 수준을 정의할 때 바로 이 세 현상을 기준으로 삼았어요. “이 수준은 더티 리드는 막지만 팬텀은 허용한다” 같은 식으로요. 즉 다음 장(격리 수준)은 이 세 유령의 조합표나 다름없습니다.

이 장은 유령들에게 이름표만 붙였습니다. 위 트레이스를 실제 두 세션에서 재현하려면 격리 수준을 일부러 낮춰야 하는데(SET TRANSACTION ISOLATION LEVEL …), 그 문법과 두 세션 실습은 격리 수준을 배우는 10장에서 손에 잡히게 다룹니다.

참고: 7장에서 만난 lost update(둘이 같은 값을 읽고-고쳐-써서 하나가 증발)와, 11장에서 만날 write skew는 이 표준 3형제와는 또 다른 결의 현상이에요. 동시성의 유령은 이 셋이 전부가 아닙니다. 하지만 이 셋이 기본기죠.

정리

  • 격리를 풀면 동시성 트랜잭션들이 서로를 훔쳐보거나 밟으며 괴현상이 생긴다.
  • 표준 3형제:
    • Dirty Read — 커밋 안 된 남의 값을 읽음(존재한 적 없는 유령 값).
    • Non-repeatable Read — 이미 읽은 그 행을 다시 읽으니 이 바뀜(남이 커밋).
    • Phantom Read — 같은 조건결과 집합이 바뀜(남이 조건에 맞는 행을 INSERT/DELETE).
  • 이 셋은 SQL 표준이 격리 수준을 정의하는 기준이다 → 10장은 이 유령들의 조합표.
  • 단, lost update·write skew처럼 표준 3형제 밖의 유령도 있다(11장).

생각해볼 질문: 7장에서 “완벽한 격리는 느리다”까진 봤죠. 이제 유령 셋의 이름도 알았고요. 그럼 정확히 어느 유령을 허용하면 얼마나 빨라지는가 — 이 교환비를 네 단계의 표로 만든 게 10장입니다. 각 단계는 어떤 유령을 풀어주고, 그 대가로 무엇을 얻을까요?

10장 · 네 개의 문 — 격리 수준

Last updated on