동시성 제어 - 트랜잭션 격리 수준

EunBeen Noh·2025년 5월 29일

Database

목록 보기
2/5

0. 동시성 문제

여러 트랜잭션이 동시에 같은 데이터에 접근하거나 변경하려고 할 때 발생하는 충돌 현상
이런 상황을 동시성 문제(Concurrency Problem)라고 하며, 이를 적절히 제어하지 않으면 다음과 같은 문제가 발생할 수 있다.

  • Dirty Read
  • Non-Repeatable Read
  • Phantom Read
  • Lost Update

1. 트랜잭션 격리 수준(Isolation Level)

트랜잭션을 서로 격리(Isolation) 해서 실행하면,
하나의 트랜잭션이 처리 중인 작업이 다른 트랜잭션에 영향을 주지 않도록 만들 수 있다.

격리 수준이란?

격리 수준은 트랜잭션이 다른 트랜잭션에서 수행한 작업을 얼마나 허용할지를 정의한다.
예를 들어, 한 트랜잭션이 아직 커밋하지 않은 데이터를
다른 트랜잭션이 읽을 수 있게 할지, 아니면 완전히 막을지를 설정할 수 있다.

격리 수준 조정이 필요한 이유

가장 간단한 해결책은 트랜잭션을 순차적으로 하나씩 처리하는 것이다.
하지만 이렇게 하면 병렬로 실행되는 트랜잭션이 없기 때문에
시스템 성능이 급격히 떨어질 수 있다.

그래서 실제 DBMS는 성능과 정합성을 모두 고려해,
다양한 격리 수준을 제공하며, SET TRANSACTION ISOLATION LEVEL 문이나
Spring에서의 @Transactional(isolation = Isolation.REPEATABLE_READ)처럼 트랜잭션에 격리 수준을 명시할 수 있다.

격리 수준과 Lock

격리 수준을 구현하기 위해 주로 Lock(잠금)을 사용한다.
읽기 작업은 쓰기 작업과 충돌할 수 있고, Lock이 길게 유지되면 성능 병목이 발생할 수 있다.

예를 들어, 두 사용자가 동시에 데이터를 수정하려고 할 때
모두에게 락을 걸면 데이터 정합성은 유지되지만 성능은 하락한다.
즉, 무조건 락을 거는 방식은 현실적인 해결책이 아니다.

락을 너무 많이 걸면 성능이 떨어진다.
락을 적게 걸면 데이터 정합성이 무너질 수 있다.

그래서 DBMS는 격리 수준을
Read Committed / Repeatable Read / Serializable 등으로 세분화하여,
트랜잭션 간 간섭을 최소화하면서도 성능을 확보하고 있다.

select 가능 여부에 따른 Lock의 종류와 특징

  • 공유 락(shared lock)
    데이터를 읽을 때 사용되는 락으로, 공유 락끼리 동시에 접근이 가능하다.
  • 배타 락(exclusive lock)
    데이터를 변경할 때 사용되는 락으로, 트랜잭션이 완료될 때까지 유지되며 해당 락이 해제되기 전까지 다른 트랜잭션이 접근할 수 없다.
수준Dirty ReadNon-Repeatable ReadPhantom Read설명
Read Uncommitted가능가능가능사실상 사용하지 않음
Read Committed불가능가능가능대부분의 RDBMS 기본 설정 (ex. Oracle)
Repeatable Read불가능불가능가능InnoDB 기본값, 같은 행은 일관된 값 보장
Serializable불가능불가능불가능가장 엄격, 트랜잭션을 순차적으로 실행

1.0 Read uncommitted(Level 0)

특징

  • 트랜잭션이 아직 커밋되지 않은 데이터도 조회할 수 있는 가장 낮은 수준의 격리 단계이다.
  • 공유 락(shared lock)이 걸리지 않기 때문에 다른 트랜잭션이 중간 작업 결과도 볼 수 있다.
  • 거의 사용되지 않는 격리 수준으로, 정합성보다는 성능을 우선시할 때만 선택된다.

장점

  • 락이 거의 없기 때문에 성능이 가장 좋다.

단점

  • 데이터 정합성을 심각하게 훼손할 수 있다.
  • 다른 트랜잭션의 임시 상태를 그대로 읽어올 수 있다.

발생 가능한 문제: Dirty Read

  • 커밋되지 않은 데이터를 다른 트랜잭션이 읽어버리는 현상
  • 예시: 트랜잭션 A가 A 계좌에 송금 중인데 아직 커밋되지 않은 상태에서, 트랜잭션 B가 해당 계좌의 잔액을 조회
    이후 A에서 롤백되면, B가 조회한 금액은 현실과 다른 오염된 데이터(Dirty Data)가 된다.

1.1 Read committed(Level 1)

특징

  • 한 트랜잭션이 커밋된 데이터만 읽을 수 있다.
  • 아직 커밋되지 않은 다른 트랜잭션의 변경 사항은 조회되지 않는다.
  • 데이터를 읽는 동안 공유 락(shared lock)이 걸리며, 읽기 완료 후 락은 해제된다.
  • Oracle의 기본 격리 수준이다.

장점

  • Dirty Read를 방지할 수 있다.
  • 대부분의 애플리케이션에서 기본 설정으로 충분하다.

발생 가능한 문제: Non-Repeatable Read

  • 같은 SELECT 쿼리를 같은 트랜잭션 내에서 두 번 실행했는데 결과가 달라지는 현상
  • 예시: 트랜잭션 A가 계좌 잔액을 조회했을 때 10,000원이었는데, 그 사이 트랜잭션 B가 송금 후 커밋하면, 다시 조회 시 20,000원이 조회됨

1.2 Repeatable Read(Level 2)

특징

  • 같은 트랜잭션 내에서 같은 데이터를 반복 조회해도 항상 동일한 결과를 보장한다.
  • SELECT 시 읽은 행 전체에 공유 락이 걸린다.
  • 트랜잭션이 끝나기 전까지 다른 트랜잭션이 해당 데이터를 수정할 수 없다.
  • MVCC (Multi Vercion Concurrency Control, 다중 버전 동시성 제어) 방식을 사용한다 (InnoDB 기본값).
  • 백업 레코드가 많아지면 성능 저하 가능성이 있다.

장점

  • Non-Repeatable Read 방지

발생 가능한 문제: Phantom Read

  • 같은 조건의 SELECT 쿼리를 두 번 실행했는데 새로운 행이 추가되거나 사라지는 현상
  • 예시: 트랜잭션 A가 "잔액 2억 이상" 조건으로 조회한 후, 트랜잭션 B가 해당 조건의 데이터를 INSERT 또는 DELETE하면 A는 다른 결과를 보게 됨

1.3 Serializable(Level 3)

특징

  • 가장 엄격한 트랜잭션 격리 수준
  • SELECT 시, 테이블 전체에 공유 락이 걸려 다른 트랜잭션이 추가/수정/삭제할 수 없음
  • 사실상 트랜잭션을 순차적으로 실행하는 것과 유사함

장점

  • Dirty Read, Non-Repeatable Read, Phantom Read 전부 방지 가능

단점

  • 성능 저하가 심각할 수 있음 (동시성 거의 없음)
  • 고성능이 필요한 환경에서는 부적절할 수 있음

동시성 문제 유형에 따른 격리 수준

문제 유형설명방지 방법
Dirty ReadA 트랜잭션이 아직 커밋되지 않은 B 트랜잭션의 데이터를 읽음Read Committed 이상
Non-Repeatable ReadA가 같은 데이터를 두 번 읽는데 중간에 B가 수정을 해서 값이 바뀜Repeatable Read 이상
Phantom ReadA가 조건에 맞는 데이터를 조회했는데, B가 중간에 데이터를 추가함Serializable
Lost Update두 트랜잭션이 동시에 같은 데이터를 수정하여 한 쪽의 결과가 덮어씌워짐Optimistic Lock or Pessimistic Lock

Non-Repeatable Read vs. Phantom Read

구분설명발생 원인
Non-Repeatable Read같은 row를 두 번 읽었을 때 값이 달라지는 현상UPDATE
Phantom Read같은 조건으로 범위 조회를 두 번 했는데 결과가 달라지는 현상INSERT, DELETE

일반적으로는

1) 대부분의 시스템 - Read Committed

현실에서는 대부분의 관계형 데이터베이스 시스템(RDBMS)이 기본적으로 Read Committed를 격리 수준으로 사용한다.
이 수준은 Dirty Read를 방지하면서도 성능 부담이 적어, 일반적인 업무 시스템에서 안정적인 선택지가 된다.

2) 성능이 중요한 시스템 - 낮은 격리 수준 + 보조적 전략 사용

격리 수준이 낮을수록 트랜잭션 간 충돌 가능성은 증가하지만, 처리량(Throughput)은 높아진다.
따라서 성능이 중요한 서비스에서는 낮은 격리 수준을 설정하되, 다음과 같은 보완 전략을 함께 사용한다.

  • 낙관적 락 (Optimistic Lock): 데이터 충돌을 사후에 감지
  • 비관적 락 (Pessimistic Lock): 미리 락을 걸어 충돌을 원천 차단
  • 버전 관리: 엔티티에 버전 필드를 두고 변경 충돌을 감지

3) 정합성이 중요한 서비스 - 높은 격리 수준

결제, 송금, 재고 관리처럼 데이터 정합성이 매우 중요한 도메인에서는 다음과 같은 방법으로 무결성을 보장한다.

  • Repeatable Read 또는 Serializable 격리 수준 설정
  • 명시적 Lock 처리 (e.g., SELECT ... FOR UPDATE)
  • 트랜잭션 재시도 또는 큐잉 방식 적용

이처럼 처리 성능보다 데이터 신뢰성이 중요한 경우, 높은 격리 수준을 적용하거나 비즈니스 로직 수준에서 정합성을 강화한다.

0개의 댓글