446. 오손 읽기 (Dirty Read - 미커밋 읽기)

⚠️ 이 문서는 트랜잭션의 고립성(Isolation)이 완전히 무너졌을 때 발생하는 가장 최악의 버그로, 남이 아직 확정(Commit)하지도 않고 쓰다만 '임시 데이터'를 훔쳐보았다가 그 데이터가 취소(Rollback)되어 버리면서 내 계산까지 전부 망가지는 '오손 읽기' 현상을 다룹니다.

핵심 인사이트 (3줄 요약)

  1. 본질: 커밋되지 않은(Uncommitted) 더러운(Dirty) 임시 데이터를 다른 트랜잭션이 읽어가는 현상이다.
  2. 위험성: 내가 읽어간 그 '임시 데이터'를 작성한 트랜잭션이 나중에 ROLLBACK을 해버리면, 나는 이 세상에 존재하지도 않는 가짜 데이터를 바탕으로 돈을 계산하는 끔찍한 금융 사고를 치게 된다.
  3. 해결책: 트랜잭션 격리 수준(Isolation Level)을 가장 낮은 Read Uncommitted에서 Read Committed 이상으로 한 단계만 올려도 완벽하게 방어된다. (현대 대부분의 RDBMS는 기본적으로 방어됨)

Ⅰ. 개요: 지워진 칠판의 글씨 (Context & Necessity)

은행에서 이런 일이 일어났다고 가정해보자.

  • T1 (직원): A의 계좌에 100만 원을 입금하려다가 실수로 '1,000만 원'을 입금했다(UPDATE). 아직 COMMIT은 안 누르고 눈치채서 취소(ROLLBACK)를 누를 준비를 하고 있다.
  • T2 (A 고객): 마침 딱 그 찰나의 순간에 A가 자기 폰으로 잔액 조회를 눌렀다. 통장에 '1,000만 원'이 찍혀있는 걸 보고(Dirty Read), 신나서 즉시 친구에게 500만 원을 이체해 버렸다(다른 트랜잭션 성공).
  • T1 (직원): 아차! 하고 아까의 1,000만 원 입금을 ROLLBACK 해서 없던 일로 만들었다.

결과: A의 원래 통장 잔고는 0원이었는데, 있지도 않은 가짜 돈 1,000만 원을 보고 친구에게 500만 원을 보냈다. A의 통장 잔고는 -500만 원이 되어버리고 은행 전산은 붕괴된다.

이 끔찍한 현상이 바로 Dirty Read다. "더럽다(Dirty)"는 말은 데이터가 아직 검증(Commit)되지 않은 쓰레기 상태라는 뜻이다.

📢 섹션 요약 비유: Dirty Read는 **'작가의 초고 훔쳐보기'**와 같습니다. 작가가 일기장에 "범인은 철수다"라고 대충 적어놨는데(Uncommitted), 내가 그걸 몰래 훔쳐보고 온 동네에 "범인이 철수래!"라고 소문을 냈죠. 그런데 작가가 나중에 "아, 철수가 아니네" 하고 지우개로 지워버리면(Rollback), 나는 세상에 존재하지도 않는 거짓말을 퍼뜨린 바보가 됩니다.


Ⅱ. Dirty Read가 발생하는 조건과 방어 ★

이 현상은 데이터베이스의 자물쇠(Lock)가 완전히 풀려있을 때만 발생한다.

1. 발생 조건 (Read Uncommitted)

  • 트랜잭션 격리 수준이 **Level 0 (Read Uncommitted)**일 때 발생한다.
  • 누군가 데이터를 수정(UPDATE) 중일 때도, 다른 사람이 그 테이블을 읽는(SELECT) 것을 100% 허용하는 가장 빠르지만 가장 위험한 상태다.

2. 방어 방법 (Read Committed)

  • 격리 수준을 **Level 1 (Read Committed)**로 올리면 완벽하게 막힌다.
  • 원리: 누군가 UPDATE 중인 데이터를 다른 사람이 SELECT 하려고 하면, DB는 수정 중인 임시 값을 보여주지 않는다. 대신 **Undo Log(441번 문서)**에 저장되어 있던 '수정되기 전의 깨끗하고 완벽한 옛날 값'을 보여준다.
  • T1이 COMMIT 도장을 쾅 찍어야만, 비로소 T2의 화면에 새로운 값이 보이게 된다.

Ⅲ. 실무에서의 취급

"Dirty Read를 실무에서 일부러 쓰는 경우가 있나요?"

  • 있긴 하다. 완벽한 정합성보다는 1초라도 빠른 응답 속도가 중요한 **조회용 통계 시스템(Admin 대시보드)**에서 가끔 쓴다.
  • 예: "오늘 현재까지 접속한 유저 수 총 몇 명이야?"
  • 이 숫자가 1~2명 틀리거나 롤백된 데이터가 섞여 들어가도 회사 매출에는 아무 타격이 없다. 이럴 땐 오히려 락(Lock) 때문에 기다리지 않고 0.01초 만에 더러운 데이터라도 팍팍 긁어오는 Read Uncommitted가 훌륭한 선택이 될 수 있다. (NOLOCK 힌트 사용)
┌──────────────────────────────────────────────────────────────┐
│           오손 읽기 (Dirty Read) 타임라인 시각화                       │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│ 시간  │ T1 (직원: 실수함)                │ T2 (고객: 조회함)            │
│ ────┼─────────────────────────┼─────────────────────────│
│ T1  │ UPDATE 잔고 = 1000만 원        │                         │
│ T2  │ (아직 COMMIT 안 함)            │ SELECT 잔고 ──▶ 1000만 원!!│
│ T3  │                             │ (1000만 원 믿고 다른 일 저지름) │
│ T4  │ ROLLBACK! (1000만 원 취소)     │                         │
│                                                              │
│ ★ 최종 결과: T2는 환상(1000만 원)을 보고 실제 비즈니스 로직을 처리해 버렸다💥│
└──────────────────────────────────────────────────────────────┘

Ⅳ. 결론

"검증되지 않은 정보는 차라리 모르는 게 낫다." Dirty Read는 ACID의 고립성(Isolation)이 완전히 파괴된 상태다. 이 문제가 너무 치명적이기 때문에, 오라클(Oracle)이나 MySQL 같은 현대의 모든 RDBMS는 아예 태어날 때부터 Read Committed 이상의 격리 수준을 기본값(Default)으로 세팅해 두고 이 버그를 원천 차단하고 있다. 백엔드 개발자라면 이 현상의 무서움을 이해하고, 웬만하면 락을 푸는 NOLOCK 옵션을 남발하지 말아야 한다.


📌 관련 개념 맵

  • 관련 특성: 고립성 (Isolation - 443번 문서)
  • 방어막 (격리 수준): READ COMMITTED (오라클 기본값)
  • 보조 기술: Undo Log (언두 로그 - Dirty Read를 막기 위해 옛날 값을 보여줄 때 사용)
  • 다음 단계의 이상 현상: Non-Repeatable Read (447번 문서)

👶 어린이를 위한 3줄 비유 설명

  1. 엄마가 내일 소풍 도시락에 '소시지'를 싸줄지 '계란말이'를 싸줄지 고민하다가 일단 메모장에 '소시지'라고 대충 적어두셨어요. (Uncommitted)
  2. 내가 몰래 그 메모장을 보고 친구들에게 "나 낼 소시지 싸 온다!"라고 다 자랑해 버렸죠. (Dirty Read)
  3. 근데 엄마가 맘이 바뀌어서 메모장에 줄을 긋고 '계란말이'로 확정(Commit) 지으시면? 나는 친구들에게 거짓말을 한 바보가 되어버린답니다!