포스트

[데이터베이스 트랜잭션 핵심 개념] 데이터베이스 격리 수준 : 동시성과 무결성 사이의 딜레마

면접을 보면서 데이터베이스 격리 수준에 대한 질문을 받고 제대로 설명을 하지 못한 경험이 있어 격리 수준에 대해서 정리해보려고 합니다.

여러 사용자가 동시에 데이터베이스에 접근하여 데이터를 읽거나 쓰는 상황이 생길때 동시성 환경에서 데이터의 정확성을 지키는 것은 매우 중요합니다. 이때 핵심적인 역할을 하는게 격리 수준 입니다.

격리 수준은 쉽게 말해, 여러 트랜잭션이 동시에 실행될 때, 서로의 작업에 얼마나 영향을 주지 않고 ‘격리’될 것인가를 정의하는 것입니다.

격리 수준을 어떻게 설정하느냐에 따라 성능이 좋아질 수도, 데이터의 신뢰성이 떨어질 수도 있습니다.


트랜잭션과 격리 수준이 왜 필요한가?

트랜잭션은 데이터베이스에서 수행되는 하나의 논리적인 작업 단위입니다. 예를 들어, 은행 계좌 이체는 ‘내 계좌에서 출금’과 ‘상대방 계좌에 입금’ 이라는 두가지 작업으로 이루어지지만, 이 두 작업이 모두 성공하거나 실패해야만 데이터의 정합성이 유지됩니다. 이를 ACID 속성이라고 하고, 그중 격리(Isolation)가 바로 이 격리 수준과 관련이 있다고 보시면 됩니다.

여러 트랜잭션이 동시에 실행 될 때, 다음 세가지 문제가 발생할 수 있습니다.

  1. 더티 리드 (Dirty Read)
    • 아직 커밋되지 않은 다른 트랜잭션의 데이터를 읽는 현상
    • 커밋되지 않은 데이터는 롤백이 될 수 있으므로 잘못된 데이터를 읽을 수 있습니다.
  2. 반복 불가능한 읽기
    • 한 트랜잭션 내에서 같은 쿼리를 두 번 실행했을 때, 그 사이에 다른 트랜잭션이 데이터를 수정하고 커밋하여 두 번의 읽기 결과가 달라지는 현상
  3. 팬텀 리드
    • 한 트랜잭션 내에서 같은 쿼리를 두 번 실행했을 때, 그 사이에 다른 트랜잭션이 새로운 레코드를 삽입/삭제하고 커밋하여 두 번의 읽기 결과로 반환되는 행의 집합이 달라지는 현상

이러한 문제들을 얼마나 방지할 것인가? 이게 바로 격리 수준이 결정하는 것입니다.

데이터베이스의 4가지 표준 격리 수준

SQL 표준에서는 4가지 표준 수준을 정의하고 있습니다. 격리 수준이 높아질 수록 동시성 제약이 강해져 데이터의 무결성이 높아지지만, 성능은 저하될 수 있습니다. (반대로 격리 수준이 낮아진다면, 무결성에 위험이 커지겠죠?)

1. READ UNCOMMITTED (가장 낮은 격리 수준)
  • 트랜잭션이 커밋되지 않은 다른 트랜잭션의 변경 사항도 읽을 수 있습니다. 마치 서류작업이 끝나지도 않았는데 다른사람이 서류를 읽고 사용하는것과 같은 상황입니다.
  • 효과 : 없음
  • 발생 가능한 문제
    • 더티 리드 (Dirty Read) 발생 가능 : 가장 치명적인 문제로, 롤백될 수 있는 데이터를 읽어 잘못된 판단을 내릴 수 있습니다.
    • 반복 불가능한 읽기, 팬텀 리드 같은 문제도 발생합니다.
  • 사용처
    • 극도의 성능이 중요하고, 데이터의 정확성이 크게 중요하지 않은 곳에서 사용하지만 거의 사용하지 않음
2. READ COMMITED (많은 DB의 기본 격리 수준)
  • 트랜잭션이 커밋된 데이터만 읽을 수 있습니다. 서류작업이 끝나고 완료 표시를 확인 후 다른 사람이 서류를 읽고 사용하는 것과 같은 상황입니다.
  • 효과 : 더티 리드를 방지합니다.
  • 발생가능한 문제
    • 반복 불가능한 읽기 발생 가능 : 한 트랜잭션 내에서 같은 행을 두 번 읽을 때, 그사이에 다른 트랜잭션이 해당 행을 수정하고 커밋하면 결과가 달라질 수 있습니다.
  • 사용처
    • 대부분의 웹 애플리케이션 등에서 가장 흔하게 사용되는 격리 수준입니다. 성능과 데이터 무결성 사이의 적절한 균형점을 제공합니다.
3. REPEATABLE READ
  • 설명 : 한 트랜잭션에서 처음 읽은 데이터가 트랜잭션이 끝날 때까지 항상 동일함을 보장합니다. 마치 내가 서류를 들고 작업하는 동안, 다른 사람이 그 서류 내용을 바꿀 수 없게 잠가두는것과 같습니다.
  • 효과 : 더티 리드, 반복 불가능한 읽기 방지
  • 발생가능한 문제
    • 팬텀 리드 발생 가능: 기존 행의 변경은 막지만, 다른 트랜잭션이 새로운 행을 삽입하거나 삭제하는 것은 막지 못할 수 있습니다. 예를 들어, WHERE 절을 사용한 쿼리를 두 번 실행했을 때, 새로운 행이 추가되어 두 번째 쿼리 결과에 나타날 수 있습니다.
    • DB 구현에 따라 팬텀 리드를 막는 경우도 있음 - MySQL의 InnoDB 스토리지 엔진은 갭 락(Gap Lock)을 사용하여 팬텀 리드도 방지합니다.
  • 사용처
    • 좀 더 높은 데이터 일관성이 요구될 때 사용됩니다.

4. SERIALIZABLE (가장 높은 격리 수준)

  • 설명 : 가장 강력한 격리 수준으로, 모든 트랜잭션을 순차적으로 실행되는 것처럼 보이게 합니다. 즉, 동시성을 허용하지 않고 모든 트랜잭션을 직렬화하여 실행하는 것과 같습니다. 마치 모든 서류 작업을 한 사람씩 줄을 서서 진행하는 것과 같습니다.
  • 효과 :
    • 더티 리드
    • 반복 불가능한 읽기
    • 팬텀 리드 (Phantom Read)까지, 위에서 언급한 모든 동시성 문제를 완벽하게 방지합니다.
  • 발생 가능한 문제 :
    • 성능 저하: 트랜잭션 간의 잠금(Locking)이 광범위하게 발생하여 동시 처리량이 현저히 줄어듭니다. 데드락(Deadlock) 발생 위험도 높아집니다.
  • 사용처 :
    • 극도의 데이터 일관성이 요구되며, 동시성이 크게 중요하지 않은 (혹은 동시성 문제를 감수할 수 있는) 상황에서 사용됩니다. (예: 금융 거래의 최종 결산, 재고 조정 등)

어떤 격리 수준을 선택해야 할까?

격리 수준의 선택은 ‘성능’과 ‘데이터 무결성/정확성’ 사이의 트레이드오프(Trade-off)입니다.

  • 대부분의 경우 READ COMMITTED 또는 REPEATABLE READ가 적절한 선택지입니다.
    • READ COMMITTED: 높은 동시성과 합리적인 데이터 무결성을 제공합니다. 더티 리드만 방지하면 되는 상황에 적합합니다.
    • REPEATABLE READ: 더티 리드와 반복 불가능한 읽기를 방지해야 할 때 사용합니다. MySQL의 InnoDB는 이 수준에서 팬텀 리드까지 방지해주어 높은 일관성을 제공합니다.
  • SERIALIZABLE은 데이터 일관성이 최우선이며 성능 저하를 감수할 수 있는, 매우 엄격한 환경에서만 고려해야 합니다.
  • READ UNCOMMITTED는 데이터 무결성을 심각하게 해치므로, 특별한 경우가 아니라면 절대 사용하지 않는 것이 좋습니다.

핵심은 “내가 다루는 데이터가 얼마나 민감하고, 어떤 종류의 동시성 문제가 절대 발생하면 안 되는가?”를 정확히 파악하는 것입니다.


마무리하며

격리 수준은 데이터베이스의 동시성 제어 메커니즘을 이해하는 데 필수적인 개념입니다. 단순히 코드를 짜는 것을 넘어, 내가 만든 애플리케이션이 실제 서비스 환경에서 어떻게 작동하고 데이터를 어떻게 다루는지 이해하는 데 큰 도움이 됩니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.