데이터베이스

트랜잭션 격리 수준에 대해 알아보자

신민석 2025. 5. 2. 04:58

🎯 트랜잭션 격리 수준이란?


트랜잭션 격리 수준(Transaction Isoliation Level) 은 데이터베이스에서 여러 트랜잭션이 동시에 실행될 때 데이터의 정합성과 일관성을 보장하기 위해 설정되는 수준 입니다. 즉, 트랜잭션 간에 어느정도까지의 범위를 허용하며 서로 작업할지를 나타내는 척도라고 할 수 있습니다.

 

트랜잭션 수준은 크게 4가지로 나뉩니다. READ UNCOMMITED -> READ COMMITED -> REPEATABLE READ -> SERIALIZABLE 순서로 격리 수준이 높아 집니다.

 

그러면 이렇게 격리 수준을 왜 나눌까에 대해 알아보기 전에 트랜잭션의 성질 부터 이해해야 합니다. 트랜잭션은 ACID 의 특징을 갖고 있습니다. 

 

  트랜잭션 ACID


 

1. Atomicity ( 원자성 ) 

 

한 트랜잭션의 연산은 모두 성공하거나 모두 실패해야 합니다. 하나라도 실패하면 전체 트랜잭션이 롤백되는 것이 특징이고 모든 작업이 최종적으로 모두 성공해야 적용되는 특징을 가지고 있습니다.

 

2.  Consistency ( 일관성 )

 

트랜잭션 실행 전과 실행 후, 모든 데이터는 일관된 상태를 유지해야 합니다.

 

3.  Isolation (격리성 )

 

동시에 실행되는 트랜잭션은 서로 간섭하지 않아야 합니다. 이를 위해 트랜잭션 격리 수준이 사용됩니다.

 

4. Durability ( 지속성 )

 

트랜잭션이 커밋되면, 그 결과는 영구적으로 저장되어야 합니다. 만약 시스템이 갑자기 다운 되더라도 커밋된 트랜잭션 결과는 보존 되어야 합니다.

 

 

ACID 중 Isolation( 격리성 ) 에 대해 다시 자세히 알아봅시다.

 

트랜잭션 수행 중 다른 트랜잭션이 끼어들 수 없다면 모든 트랜잭션은 순차적으로 처리될 것이고 데이터의 정합성은 보장됩니다. 하지만 이러한 과정은 트랜잭션의 양이 많아지면 어쩔 수 없이 기다려야 하는 대기 시간이 늘어나고 결국 치명적인 성능 저하로 이어질 수 있습니다.

 

그래서 필요한 것이 완전한 트랜잭션 격리가 아닌 완화된 수준의 격리가 필요하고, 속도와 데이터 정합성에 대한 트레이드 오프를 고려하여 트랜잭션의 격리성 수준을 나눈 것이 바로 트랜잭션 격리 수준입니다.

 

 

🎯 각각의 격리 수준 단계


자, 이제 어떤 트랜잭션 수준이 있는지 알아봅시다. READ UNCOMMITED, READ COMMITED, REPEATABLE READ,  Serializable  총 4개의 수준이 있습니다. 위 순서는 격리 수준이 낮은 순서대로 나열한 것입니다. 

 

 

  READ UNCOMMITED ( 커밋되지 않은 읽기)

 

다른 트랜잭션에서 커밋되지 않은 데이터에 접근할 수 있는 하는 격리 수준입니다. 가장 저수준의 격리이며 일반적으로 잘 사용하지 않습니다. 예를들어 사용자 A 의 트랜잭션에서 INSERT 를 통해 데이터를 추가했을때 아직 커밋 되지 않은 상태임에도 불구하고 READ UNCOMMITED 는 변경된 데이터에 접근할 수 있습니다.

 

 

위 그림에서 볼 수 있듯이 트랜잭션 A 가 새로운 데이터를 INSERT 하고 커밋하기 전 이지만 트랜잭션 B 는 새로 INSERT 된 id 가 12 인 huk 을 조회할 수 있습니다.

 

이처럼 트랜잭션이 완료되지 않았는데 다른 트랜잭션에서 볼 수 있는 부정합 문제를 Dirty Read( 오손 읽기 ) 라고 합니다. Dirty Read 는 데이터가 조회되었다가 사라지는 현상이 발생해 상당한 혼란을 줄 수 있기에 RDBMS 표준에서 인정하지 않을 정도로 문제가 많은 격리 수준입니다.

 

 

  READ COMMITED ( 커밋된 읽기)

 

다른 트랜잭션에서 커밋된 데이터로만 접근할 수 있게 하는 격리 수준입니다. MySQL 을 제외한 대부분이 READ COMMITED 격리 수준을 사용합니다. 만약 A 트랜잭션이 "Hi" 라는 데이터를 "Hello" 로 업데이트 한 후 커밋하지 않았을때 B 트랜잭션은 해당 데이터를 어떻게 조회할까 ? "Hi" 라는 값이 조회됩니다. 즉, Dirty Read 가 발생하지 않습니다. B 트랜잭션이 이와 같은 데이터를 조회한 이유는 바로 Undo 영역 때문입니다.  

 

 

 

Undo는 변경 전의 데이터가 저장된 영역이고, Commit 하기 전 데이터를 읽어올 수 있는 이유는 Undo 영역에 있는 데이터를 읽어들어오기 때문입니다.

 

READ COMMITED 에서는 None Repeatable Read( 반복 가능하지 않은 읽기 ) 현상이 발생할 수 있습니다. 이는 하나의 트랜잭션에서 동일한 SELECT 쿼리를 실행했을때 다른 결과가 나타날 수 있는 현상입니다. 만약 트랜잭션 B 에서 총 두번의 쿼리를 날린다고 생각해봅시다. 첫번째 쿼리에서는 A 트랜잭션이 커밋되기 전이면 "HI" 를 Undo 로그에서 읽어옵니다. 그 다음 A 트랜잭션이 커밋되고 트랜잭션 B 가 두번째 쿼리로 조회하면 "Hello" 를 조회해 하나의 트랜잭션에서 데이터 정합성 문제가 발생합니다. 이러한 상황이 바로 None Repeatable Read 현상 입니다.    이러한 현상을 방지할 수 있는 격리 단계가 바로 아래에서 설명할 Repeatable Read ( 반복 가능한 일기) 입니다.

 

 

  Repeatable Read ( 반복 가능한 읽기 )

 

None Repeatable Read 를 해결하는 격리 수준으로, 커밋된 데이터만 읽을 수 있되 자신보다 낮은 트랜잭션 번호를 갖는 트랜잭션에서 커밋한 데이터만 읽는 격리 수준입니다. 트랜잭션 ID 를 통해 Undo 영역에서의 데이터를 스냅샷 처럼 관리하여 동일한 데이터를 보장하는 것을 MVCC ( Multi Version Concurrency Control) 라고 합니다. 아래 그림을 통해 자세히 알아봅시다.

 

 

 

트랜잭션 B 는 트랜잭션 A 가 시작하기전 이미 시작된 상태 입니다. 이때 REPEATED READ 는 트랜잭션 번호를 참고해 자신보다 먼저 실행된 트랜잭션 데이터만을 조회합니다. 만약 테이블에 자신보다 이후에 실행된 데이터가 존재하면 Undo 로그를 참고해 데이터를 조회합니다. 이로 인해 트랜잭션 A 가 커밋까지 성공했지만, A 트랜잭션은 B 트랜잭션보다 나중에 실행되었기 때문에 트랜잭션 B 에서는 기존과 동일한 데이터를 얻을 수 있습니다. 즉, 하나의 트랜잭션 내부에서 데이터 정합성을 안정적으로 확보할 수 있습니다. READ COMMIT 격리 수준에서의 None Reppeatable Read (반복 읽기 불가능 ) 현상을 해결한거죠,

 

이로써 하나의 트랜잭션 내부에서 데이터 정합성을 안정적으로 확보할 수 있습니다. 

 

하지만, REPEATABLE READ 는 새로운 레코드의 추가까지는 막지 못합니다. SELECT 로 조회한 경우 트랜잭션이 끝나기 전에 다른 트랜잭션에 의해 추가된 레코드를 발견할 수 있는데 이를 Phantom Read(유령 읽기) 라고 합니다. 하지만 MVCC 덕분에 일반적인 조회에서 유령 읽기가 발생하지 않습니다. 자신보다 나중한 트랜잭션이 추가한 레코드는 무시하며 유령 읽기를 방지합니다.

 

 

 어떤 상황에서 유령 읽기가 REPEATEABLE READ 에서 발생하는지는 설명드릴 내용이 많아 다른 포스팅에서 설명드리겠습니다.

 

 

  Serializable 

 

가장 고 수준의 격리 수준입니다. 모든 트랜잭션을 순차적으로 실행시켜 격리 수준중 가장 높은 데이터 안정성을 확보할 수 있습니다. 하지만 동시 처리가 불가능해 처리 속도가 매우 느립니다. 

 

Dirty Read 와 None-Repeatable Read, Phantom Read 와 같은 모든 이상 현상이 방지되는 것이 특징이고, 내부적으로는 락(Lock) 기반 또는 MVCC 기반으로 동작합니다.

 

락(Lock) 기반 : 모든 쓰기/읽기 작업에 대해 공유락(Share Lock) 또는 Exclusive Lock ( 배타락 ) 이 사용됩니다.

MVCC 기반 : 충돌 가능성이 있는 트랜잭션을 실행 중단(Roll Back) 시켜 직렬성을 유지 합니다.

 


 

정리해보면, 격리 수준이 높은 MySQL(REPETABLE READ) 서버의 처리 성능이 떨어져 보일 수 있으나, 실제로는 SERIALIZABLE 이 아닌 이상 크게 성능에 문제는 없습니다. 결국 Undo 로그를 통해 레코드를 참조하는 과정이 거의 동일하기 때문입니다. 따라서 MYSQL 은 갭락을 통해 Phantom Read 까지 거의 발생하지 않고,  READ COMMITTED 보다는 정합성이 뛰어는 REPEATABLE READ 를 사용합니다.