본문 바로가기
데이터베이스

[트랜잭션 시리즈 - 1] 트랜잭션의 격리 수준 정리

by 시니성 2025. 3. 24.

이번 글 에서는 트랜잭션의 4가지 격리 수준에 대해 정리해 보겠습니다.

왜냐하면, 최근 봤던 기술 면접에서 제가 이 부분을 전혀 대답하지 못했었기 때문입니다. 따흑 ㅠㅠ

이번 글 에서는 트랜잭션 격리 수준에 대해 정리하고, 다음 글 에서는 각 격리 수준별로 트랜잭션 일관성이 깨질 수 있는 상황에 대해 가상 시나리오를 예로 들어 좀 더 피부에 와닿게 정리해 보도록 하겠습니다.

데이터베이스 시스템에서 동시성 제어는 매우 중요한 요소입니다.
여러 사용자나 프로세스가 동시에 데이터에 접근할 때, 데이터의 일관성을 유지하면서도 성능을 최적화하는 것은 쉽지 않은 과제입니다.
이를 위해 데이터베이스는 '트랜잭션 격리 수준(Transaction Isolation Level)'이라는 개념을 제공합니다.

트랜잭션 격리 수준이란?

트랜잭션 격리 수준은 동시에 실행되는 여러 트랜잭션이 서로에게 어떤 영향을 미치는지 정의하는 규칙입니다.
SQL 표준(ANSI/ISO SQL)에서는 4가지 격리 수준을 정의하고 있으며, 각 수준마다 동시성과 데이터 일관성 간의 균형이 다릅니다.

 

https://alxibra.medium.com/isolation-level-in-rails-847edc9e347d

트랜잭션 격리 수준의 종류

1. READ UNCOMMITTED (커밋되지 않은 읽기)

가장 낮은 격리 수준입니다.
한 트랜잭션에서 커밋되지 않은 변경사항을 다른 트랜잭션에서 읽을 수 있습니다.

특징:

  • 다른 트랜잭션이 아직 커밋하지 않은 데이터도 읽을 수 있음
  • 많은 동시 요청을 처리할 수 있지만, 데이터 정확성에 대한 보장이 낮음
  • 데이터베이스 락(lock)을 최소화하여 성능 향상

발생 가능한 문제:

  • Dirty Read: 다른 트랜잭션에 의해 변경되었지만 아직 커밋되지 않은 데이터를 읽는 현상. 만약 원래 트랜잭션이 롤백되면, 읽은 데이터는 무효한 데이터가 됨
  • Non-repeatable Read: 한 트랜잭션 내에서 같은 쿼리를 두 번 실행했을 때 다른 결과가 나오는 현상
  • Phantom Read: 한 트랜잭션 내에서 같은 쿼리를 두 번 실행했을 때, 처음에는 없던 레코드가 두 번째 쿼리에서 나타나는 현상

2. READ COMMITTED (커밋된 읽기)

많은 데이터베이스 시스템의 기본 격리 수준입니다.
트랜잭션은 다른 트랜잭션이 커밋한 데이터만 읽을 수 있습니다.

특징:

  • 커밋된 데이터만 읽을 수 있어 Dirty Read 방지
  • 읽기 작업에 대해 공유 락(shared lock)을 설정하지만, 쿼리가 완료되면 바로 해제

발생 가능한 문제:

  • Non-repeatable Read: 여전히 발생 가능
  • Phantom Read: 여전히 발생 가능

3. REPEATABLE READ (반복 가능한 읽기)

MySQL InnoDB의 기본 격리 수준입니다.
트랜잭션 내에서 같은 쿼리를 여러 번 실행해도 항상 같은 결과를 보장합니다.

특징:

  • 트랜잭션이 시작되면 해당 트랜잭션이 읽은 모든 데이터에 대해 읽기 락(read lock)을 유지
  • 다른 트랜잭션의 데이터 변경을 차단하여 일관된 결과 보장

발생 가능한 문제:

  • Phantom Read: 일부 DBMS에서는 여전히 발생 가능

4. SERIALIZABLE (직렬화 가능)

가장 높은 격리 수준으로, 완벽한 데이터 일관성을 제공합니다.

특징:

  • 트랜잭션이 순차적으로 실행되는 것처럼 동작
  • 모든 동시성 문제(Dirty Read, Non-repeatable Read, Phantom Read) 방지
  • 범위 락(range locks)을 사용하여 쿼리 조건에 해당하는 모든 데이터에 락 설정

발생 가능한 문제:

  • 심각한 성능 저하 발생 가능
  • 데드락(deadlock) 발생 가능성 증가

트랜잭션 격리 수준 비교표

격리 수준 Dirty Read Non-repeatable Read Phantom Read 동시성 데이터 일관성
READ UNCOMMITTED 발생 발생 발생 매우 높음 매우 낮음
READ COMMITTED 방지 발생 발생 높음 중간
REPEATABLE READ 방지 방지 발생* 중간 높음
SERIALIZABLE 방지 방지 방지 낮음 매우 높음

*일부 데이터베이스(MySQL InnoDB 등)에서는 REPEATABLE READ 수준에서도 Phantom Read를 방지함

주요 DBMS별 기본 격리 수준

  • PostgreSQL: READ COMMITTED
  • MySQL (InnoDB): REPEATABLE READ
  • Oracle: READ COMMITTED
  • SQL Server: READ COMMITTED
  • H2: READ COMMITTED

격리 수준 선택 기준

적절한 격리 수준을 선택하는 것은 애플리케이션의 요구사항과 성능 목표에 따라 달라집니다.

  1. READ UNCOMMITTED
    • 매우 높은 처리량이 필요하고 데이터 일관성이 크게 중요하지 않은 경우
    • 예: 대략적인 통계 계산, 실시간 대시보드
  2. READ COMMITTED
    • 일반적인 웹 애플리케이션
    • 높은 동시성과 적당한 데이터 일관성 균형이 필요한 경우
  3. REPEATABLE READ
    • 트랜잭션 내에서 일관된 데이터 스냅샷이 필요한 경우
    • 복잡한 보고서 생성, 정산 처리 등
  4. SERIALIZABLE
    • 금융 거래, 결제 처리와 같이 데이터 정확성이 절대적으로 중요한 경우
    • 동시 사용자가 적거나 성능보다 데이터 무결성이 우선시되는 경우
    • "확인 후 삽입(check-then-insert)" 패턴이 사용되는 경우

Spring에서의 트랜잭션 격리 수준 설정

Spring Framework에서는 @Transactional 애노테이션을 사용하여 트랜잭션 격리 수준을 설정할 수 있습니다:

import org.springframework.transaction.annotation.Isolation
import org.springframework.transaction.annotation.Transactional

@Transactional(isolation = Isolation.SERIALIZABLE)
fun createUniqueRecord(id: String, data: String): Record {
    // 레코드가 없는지 확인
    if (repository.findById(id) == null) {
        // 새 레코드 삽입
        return repository.save(Record(id, data))
    }
    throw DuplicateKeyException("Record with ID $id already exists")
}

결론

트랜잭션 격리 수준은 데이터베이스 애플리케이션 설계에서 중요한 결정 사항입니다.
가장 적합한 격리 수준을 선택하려면 다음 요소를 고려해야 합니다.

  1. 데이터 일관성 요구사항: 애플리케이션에서 데이터 정확성이 얼마나 중요한가?
  2. 동시성 요구사항: 시스템이 얼마나 많은 동시 트랜잭션을 처리해야 하는가?
  3. 성능 요구사항: 응답 시간과 처리량에 대한 기대치는 무엇인가?
  4. 동시성 문제 발생 가능성: 애플리케이션의 특성상 동시성 문제가 발생할 가능성이 높은가?

많은 경우 READ COMMITTED가 적절한 기본값이지만, "확인 후 삽입" 패턴이나 코드 생성과 같은 특정 사례에서는 SERIALIZABLE을 고려해야 합니다.
트랜잭션 격리 수준을 선택할 때는 항상 데이터 일관성과 성능 간의 균형을 고려하는 것이 중요합니다.
그럼 다음 글에서는 트랜잭션 일관성이 깨지게 되는 대표적인 케이스와 어떤 격리 수준에서 그러한 이슈가 발생하는지 정리하는 글로 다시 찾아 뵙겠습니다.

728x90