dev-sohee 님의 블로그

트랜잭션 격리 수준이란? 데이터베이스 일관성의 비법 본문

트랜잭션 격리 수준이란? 데이터베이스 일관성의 비법

dev-sohee 2024. 8. 24. 14:10

데이터베이스에서 트랜잭션은 여러 작업을 하나의 논리적 단위로 묶어, 모두 성공하거나 모두 실패하도록 보장합니다. 이를 통해 데이터의 일관성과 무결성을 유지할 수 있습니다. 하지만 여러 트랜잭션이 동시에 실행될 때, 데이터의 일관성을 어떻게 유지할지가 중요한 문제가 됩니다. 이때 등장하는 개념이 바로 트랜잭션 격리 수준(Isolation Level)입니다. 격리 수준은 각 트랜잭션이 다른 트랜잭션의 중간 결과를 얼마나 볼 수 있는지를 결정하며, 성능과 데이터 무결성 사이의 균형을 맞추는 데 중요한 역할을 합니다.

* 트랜잭션 격리 문제
* 트랜잭션 격리 수준

출처_https://blog.skby.net/

 

예를 들어, 다음과 같은 상황에서 트랜잭션 격리가 필요합니다. 사용자 A가 자신의 계좌에서 B의 계좌로 2000원을 이체하려고 합니다. 이 작업은 두 개의 단계로 나눌 수 있습니다: A의 계좌에서 2000원을 출금하고, B의 계좌에 2000원을 입금하는 것입니다.

  • 문제: 만약 두 단계 중 하나만 완료되고, 나머지 단계가 실패하면, A의 계좌에서는 돈이 빠져나갔지만 B의 계좌에는 입금되지 않는 불일치가 발생할 수 있습니다.
  • 해결: 트랜잭션 격리 수준을 통해 이 두 작업을 하나의 트랜잭션으로 묶어 처리하여, 중간에 문제가 발생할 경우 전체 작업이 롤백되도록 합니다. 이를 통해 데이터의 일관성을 유지할 수 있습니다.

출처_https://codechasseur.tistory.com/29

 

 

트랜잭션 격리 문제

  • Dirty Read
    : 하나의 트랜잭션이 다른 트랜잭션에서 아직 커밋되지 않은 데이터를 읽는 상황을 말합니다.
    ex) 트랜잭션 A가 특정 데이터 값을 100에서 200으로 수정하고 아직 commit되지 않았습니다. 이때, 트랜잭션 B가 해당 데이터를 읽으면 200이라는 값을 얻게 되는데, 만약 트랜잭션 A가 이후에 롤백된다면 트랜잭션 B는 존재하지 않는 잘못된 데이터를 읽은 셈이 됩니다.
  • Non-repeatable Read
    : 동일한 트랜잭션 내에서 같은 데이터를 두 번 읽었을 때, 그 사이에 다른 트랜잭션이 해당 데이터를 수정하거나 삭제하여 서로 다른 결과를 얻는 상황입니다.
    ex) 트랜잭션 A가 특정 데이터 값을 100으로 읽었습니다. 이후 트랜잭션 B가 그 값을 200으로 수정하고 commit했습니다. 트랜잭션 A가 다시 동일한 데이터를 읽으면 200이 되어, 처음 읽었을 때와 다른 결과를 얻게 됩니다.
  • Phantom Read
    : 동일한 트랜잭션 내에서 범위 쿼리를 실행할 때, 다른 트랜잭션이 새로운 데이터를 삽입하거나 기존 데이터를 삭제하여, 쿼리 결과의 레코드 집합이 달라지는 상황을 말합니다. 
    ex) 트랜잭션 A가 특정 조건에 맞는 모든 레코드를 조회하는 쿼리를 실행하여 10개의 결과를 얻었습니다. 이후 트랜잭션 B가 그 조건에 맞는 새로운 레코드를 추가하고 commit했습니다. 트랜잭션 A가 동일한 쿼리를 다시 실행하면 결과가 11개로 늘어납니다. 이처럼 트랜잭션 A가 "phantom" 데이터 레코드를 경험하게 되는 것입니다.


트랜잭션 격리 수준(Isolation Level)
이란 여러 트랜잭션이 동시에 처리될 때, 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있게 허용할지 여부를 결정하는 것입니다. 트랜잭션의 격리 수준은 격리 수준의 순서대로 Serializable, Repeated Read, Read Committed, Read Uncommitted 가 존재합니다. 

 

SERIALIZABLE
: 가장 엄격한 격리 수준으로, 이름 그대로 트랜잭션을 순차적으로 진행시키기 때문에 동시성 문제가 완전히 해결됩니다. 가장 안전하지만 가장 성능이 떨어지므로, 극단적으로 안전한 작업이 필요한 경우가 아니라면 사용해서는 안됩니다.

  • 장점
    : Dirty Read, Non-repeatable Read, Phantom Read 등 모든 트랜잭션 격리 문제를 방지하고 데이터의 완전한 일관성을 보장합니다.
  • 단점
    : 모든 트랜잭션이 순차적으로 실행되므로, 성능이 매우 낮아질 수 있습니다. 동시성이 크게 제한되어, 실시간 처리 시스템이나 대규모 사용자 기반 시스템에서는 잘 사용되지 않습니다.

 

REPEATABLE READ
: 트랜잭션은 고유한 번호를 가지며 UNDO 영역에 백업된 모든 레코드에는 변경을 발생시킨 트랜잭션의 번호가 포함되어 있습니다. 하나의 트랜잭션 안에서 일어나는 모든 SELECT 쿼리는 자신의 트랜잭션 번호보다 작은 트랜잭션 번호에서 변경한 사항들만 볼 수 있습니다. 이런 방식을 MVCC(Multiversion Concurrency Control)라고 합니다.

**MVCC란? : DBMS에서 서로 다른 세션이 동일한 데이터에 접근했을 때 각 세션마다 스냅샷 이미지를 보장해주는 매커니즘입니다. RDBMS에서 동시성을 높이기 위해 등장한 기술입니다. MVCC의 가장 큰 목적은 Lock을 사용하지 않는 일관된 읽기를 제공하기 위함입니다. 또한, 데이터의 여러 버전을 유지함으로써, 다수의 트랜잭션이 동시에 데이터에 접근할 수 있어 동시성을 크게 향상시켜줍니다. 하지만, 추가적인 저장공간이 필요하며 오래된 데이터 버전을 정리하는 과정이 필요합니다.

출처_https://velog.io/@chiyongs/

 

위 그림을 보시면  자신의 트랜잭션 번호보다 작은 트랜잭션 번호에서 변경한 사항들만 볼 수 있기 때문에 트랜잭션 B가 트랜잭션 A에서 변경한 name = 'kakao'를 참조하지 않고 UNDO 영역의 'samsung'을 참조한 것을 알 수 있습니다.

  • 장점
    : Non-repeatable Read를 방지합니다. 트랜잭션이 실행되는 동안 같은 데이터를 반복적으로 읽어도 동일한 결과가 보장됩니다.
  • 단점
    : 트랜잭션 내에서 범위를 포함한 쿼리를 실행할 때, Phantom Read가 발생할 수 있습니다. 예를 들어, 아래 그림을 보시면 위 그림과 비슷해 보이지만 아래 상황에서는 SELECT FOR UPDATE 쿼리를 사용했습니다.

출처_https://velog.io/@chiyongs/

 

이 경우에도 MVCC를 통해 해결될 것 같지만, 두 번째 실행되는 SELECT FOR UPDATE 때문에 그럴 수 없습니다. 왜냐하면 SELECT FOR UPDATE 쿼리는 해당 레코드에 write lock을 거는데, UNDO 영역에는 Lock을 걸 수 없기 때문에 현재 레코드의 값을 가져오게 됩니다. 따라서 SELECT FOR UPDATE로 레코드를 조회하는 경우에는 UNDO 영역의 데이터가 아니라 테이블의 레코드를 가져오게 되고, 이로 인해 Phantom Read가 발생하는 것입니다.

 

READ COMMITTED
: 하나의 트랜잭션은 다른 트랜잭션이 commit한 데이터만 읽을 수 있습니다. 즉, 트랜잭션 A가 데이터를 수정하고 commit하기 전에는 트랜잭션 B가 해당 데이터를 읽을 수 없습니다.

  • 장점
    : Dirty Read를 방지합니다. 데이터가 커밋된 후에만 다른 트랜잭션에서 읽을 수 있으므로, 데이터 무결성이 잘 유지됩니다. 성능과 일관성의 균형이 잘 맞춰져 있어, 다양한 응용 프로그램에 적합합니다.
  • 단점
    : Non-repeatable Read가 발생할 수 있습니다. 아래 예시처럼 동일한 SELECT 쿼리에 다른 결과를 가져오는 경우가 발생합니다.

출처_https://velog.io/@chiyongs/

 

READ UNCOMMITED
: 트랜잭션이 다른 트랜잭션에서 아직 커밋되지 않은 데이터를 읽을 수 있는 가장 낮은 수준의 격리입니다. 모든 트랜잭션은 서로의 변경사항을 실시간으로 볼 수 있습니다. 아래 그림처럼 트랜잭션 A에서의 데이터 변경이 commit되지 않았는데 트랜잭션 B에서 변경된 데이터를 조회해서 사용 가능합니다.

출처_https://velog.io/@chiyongs/

  • 장점
    : 성능이 매우 뛰어납니다. 데이터베이스가 트랜잭션 간에 거의 동기화할 필요가 없기 때문에, 처리 속도가 빠릅니다.
  • 단점
    : Dirty Read가 발생할 수 있습니다. 데이터 일관성이 매우 낮아, 중요한 시스템에서는 거의 사용되지 않습니다.

 

트랜잭션 격리 수준을 학습하면서, 데이터베이스에서 일관성과 성능이 항상 상충한다는 점이 인상 깊었습니다. 실제 운영 환경에서는 완벽한 일관성을 보장하기 위해 모든 트랜잭션을 Serializable 수준으로 설정하는 것이 이상적이겠지만, 대부분의 경우 이는 비현실적입니다. 성능 저하와 리소스 문제 때문에 적절한 타협이 필요하며, 많은 상용 시스템이 기본적으로 Read Committed나 Repeatable Read 수준을 채택하는 이유를 이해하게 되었습니다.