본문 바로가기

기록

데이터베이스 격리 수준(Isolation Level) 알아보기

익숙함에 속아 놓쳐버린 CS 핵심 기본기

최근 기술 면접을 준비하며 스스로에게 질문을 던졌을 때, 등골이 서늘해지는 경험을 했다. 바로 ‘데이터베이스 격리 수준’에 대한 질문이었다.

분명 과거 전공 수업을 들으며 열심히 외웠던 내용이었다. Dirty Read가 무엇이고, Phantom Read가 무엇인지 분명 알고 있다고 생각했다. 하지만 막상 “각 단계의 차이를 설명하고, 왜 그런 단계가 필요한지 말해보라”는 질문 앞에서는 말문이 막혔다.

왜 제대로 기억하지 못하고 있을까 생각해보면, 아무래도 시험을 위한 공부였던 것, 그리고 실무에서는 프레임워크나 DB가 제공하는 기본값에 의존하여, 이 부분을 깊게 신경 쓰지 않아도 기능이 돌아갔기 때문이다. 트랜잭션의 깊은 곳에서 일어나는 일들은 ‘블랙박스’처럼 여겨졌고, 기억 속에서 희미해졌다.

하지만 ‘아는 것 같은 느낌’과 ‘진짜 아는 것’은 천지 차이다. 이번 기회에 흐릿해진 지식을 확실하게 정리하고, 다시는 헷갈리지 않도록 기록해 두려 한다.

격리 수준은 ‘타협’의 산물이다

본격적인 단계 설명에 앞서, “왜 데이터베이스는 격리 수준을 여러 단계로 나누었을까?”라는 근본적인 질문을 던져보자.

가장 이상적인 데이터베이스는 모든 트랜잭션을 줄 세워서 하나씩 처리하는 것이다. 그러면 데이터가 꼬일 일이 전혀 없다. 하지만 이렇게 하면 성능(동시성)이 저하된다. 반대로 모든 트랜잭션을 동시에 처리하게 하면 성능은 좋겠지만, 데이터의 정확성(무결성/정합성)이 깨지게 된다.

결국 격리 수준이란, 데이터의 정합성과 동시성(성능) 사이의 줄타기를 하는 것이다. 레벨이 높을수록 데이터는 안전하지만 느려지고, 레벨이 낮을수록 빠르지만 데이터의 안정성이 낮아진다.

격리 수준을 구분하는 기준: 3가지 문제점

격리 수준을 이해하려면 먼저 “격리가 안 됐을 때 어떤 사고가 나는지”를 알아야 한다. 이 3가지 문제점의 허용 여부가 곧 격리 수준의 기준이 된다.

  1. Dirty Read(오염된 읽기)
    • 다른 트랜잭션이 데이터를 수정하고 아직 커밋하지 않았는데, 그 값을 내가 읽을 수 있는 현상이다.
    • 만약 그 트랜잭션이 롤백되어 버린다면? 나는 존재하지도 않는 가짜 데이터를 읽고 로직을 수행한 셈이 된다.
  2. Non-Repeatable Read(반복 불가능한 읽기)
    • 한 트랜잭션 안에서 똑같은 쿼리를 두 번 날렸는데, 그 사이에 다른 트랜잭션이 값을 수정해서 서로 다른 결과가 나오는 현상이다.
    • “하나의 트랜잭션 내에서는 같은 데이터가 조회되어야 한다”는 일관성이 깨진다.
  3. Phantom Read(유령 읽기)
    • 한 트랜잭션 안에서 일정 범위의 데이터를 조회했는데, 그 사이에 다른 트랜잭션이 데이터를 추가해서 아까는 없던 데이터가 갑자기 튀어나오는 현상이다.
    • 마치 유령(Phantom)처럼 데이터가 생겼다 없어졌다 한다.

4단계 격리 수준 파헤치기(ANSI 표준)

위 문제들을 어디까지 막아줄 것인가에 따라 4단계로 나뉜다.

Level 0. Read Uncommitted(커밋되지 않은 읽기)

“상대방이 뭘 하든 다 보여준다.”

  • 특징: 가장 낮은 격리 수준이다. 다른 트랜잭션이 커밋하지 않은 데이터도 읽을 수 있다.
  • 문제점: Dirty Read를 포함한 모든 문제가 발생한다. 데이터의 신뢰성을 보장할 수 없기 때문에 실무에서는 거의 사용하지 않는다. 정확성보다 대략적인 통계나 로그 수집이 훨씬 중요할 때에 제한적으로 사용한다.

Level 1. Read Committed(커밋된 읽기)

“확정된 것만 읽는다.”

  • 특징: 오라클, PostgreSQL, SQL Server 등 많은 RDBMS의 기본 설정이다. 다른 트랜잭션이 커밋을 완료한 데이터만 조회할 수 있다.
  • 해결: Dirty Read가 발생하지 않는다. 커밋 전 데이터는 UNDO 영역(백업공간)에 있는 이전 값을 보여준다.
  • 한계: Non-Repeatable Read는 발생할 수 있다. 내가 조회하고 있는 와중에 누가 수정하고 커밋해버리면, 다시 조회했을 때 값이 바뀐다.

Level 2. Repeatable Read(반복 가능한 읽기)

“트랜잭션이 끝날 때까지 내 세상은 변하지 않는다.”

  • 특징: MySQL(InnoDB)의 기본 설정이다. 트랜잭션이 시작된 시점의 상태를 스냅샷처럼 유지한다.
  • 해결: Non-Repeatable Read가 발생하지 않는다. 트랜잭션 ID를 확인하여, 나보다 나중에 시작된 트랜잭션이 변경한 내용은 무시하고 처음 읽었던 데이터를 계속 보여준다(MVCC 기술 활용).
  • 한계: 이론적으로는 Phantom Read가 발생할 수 있다. 단, MySQL InnoDB는 Next-Key Lock 등을 이용해 이 단계에서도 Phantom Read를 대부분 방지한다.

Level 3. Serializable(직렬화 가능)

“한 줄로 서시오.”

  • 특징: 가장 엄격한 수준이다. 읽기 작업에도 잠금(Lock)을 걸어버려, 내가 읽고 있는 동안에는 누구도 데이터를 건드릴 수 없다.
  • 해결: 모든 동시성 문제가 사라진다. 가장 안전하다.
  • 치명적 단점: 동시 처리 성능이 급격히 떨어진다. 데드락에 걸릴 확률이 매우 높다. 정말 특수한 경우가 아니라면 잘 쓰지 않는다.

요약

지금까지 살펴본 내용을 표로 정리하면 다음과 같다.

격리 수준 Dirty Read Non-Repeatable Read Phantom Read 특징
Read Uncommitted O O O 성능 1등, 안정성 꼴등
Read Committed X O O 가장 보편적인 기본값
Repeatable Read X X MySQL 기본값, 정합성 높음
Serializable X X X 안정성 1등, 성능 꼴등

(O: 발생 가능 / X: 발생 안 함 / △: DB별 상이)

당연함 뒤에 숨겨진 치열함을 마주하며

단순히 면접 질문을 방어하기 위해 시작한 공부였지만, 글을 정리하며 그동안 무심코 넘겼던 COMMIT 로그 한 줄의 무게가 다르게 느껴졌다. 이전에는 데이터베이스가 알아서 데이터를 잘 지켜주는 ‘안전한 금고’라고만 생각했다. 하지만 그 내부를 들여다 보니, 수많은 트랜잭션이 동시에 쏟아지는 전쟁터 같은 상황 속에서 데이터의 무결성을 지키기 위해 치열하게 ‘격리’하고 ‘타협’하는 과정이 있었다. 우리가 편하게 비즈니스 로직에만 집중할 수 있었던 건, 묵묵히 성능과 정합성 사이에서 줄타기를 해온 데이터베이스의 고군분투 덕분이었다.

이제는 프로젝트의 DB 설정을 볼 때 단순히 “기본값이니까 쓰자”라고 넘기기보다, ”우리 서비스는 어느 정도의 불안정성을 감수할 수 있는가?”, “여기서 격리 수준을 높이면 동시성 처리에 문제는 없을까?”라는 질문을 먼저 던지게 될 것 같다.

나처럼 면접장에서, 혹은 실무에서 “왜?”라는 질문에 말문이 막혔던 분들에게 이 정리가 다시 한번 기본기를 다지는 계기가 되었으면 좋겠다. 혹시 글 내용 중 잘못 이해한 부분이 있거나 보완이 필요하다면 언제든 댓글로 알려주길 바란다.

'기록' 카테고리의 다른 글

Redis로 분산 락 구현하기(feat. KISA 보안 취약점 방어)  (1) 2025.12.14
MVCC(Multi-Version Concurrency Control) 알아보기  (0) 2025.12.08
2024년 9월 회고  (10) 2024.10.11
2024년 8월 회고  (6) 2024.09.08
2024년 7월 회고  (0) 2024.08.18