트랜잭션 격리 수준이란 무엇이고 왜 필요할까

해로(김선호)·2023년 5월 30일
1

CS / 데이터베이스

목록 보기
1/1

(이미지 출처)

들어가며

트랜잭션 격리 수준이란, 한 트랜잭션이 사용 중인 데이터에 대해 다른 트랜잭션의 접근 허용 정도를 결정하는 것을 말한다. 트랜잭션 격리 수준은 아래와 같이 4단계로 나뉘며, 격리 수준을 조절함으로써, 복수의 트랜잭션이 같은 데이터를 읽고 쓰면서 생기는 Dirty Read, Non-Repeatable Read, Phantom Read 와 같은 문제들을 해결할 수 있다.

  • Read Uncommitted(Level 0)
  • Read Committed(Level 1)
  • Repeatable Read(Level 2)
  • Serializable(Level 3)

격리 수준이 높아질수록 동시성(Concurrency)은 낮아지지만, 이에 반비례하여 데이터 일관성(Consistency)은 높아진다. 본 포스팅에서는 각 격리 수준을 살펴보며, 각 단계에서 어떤 문제들을 해결할 수 있는지 알아본다.


트랜잭션 격리 수준

Read Uncommitted (Level 0)

Read Uncommitted는 커밋되지 않은 데이터를 다른 트랜잭션이 조회할 수 있도록 허용하는 격리 수준이다. 따라서 커밋되지 않은 데이터를 조회함으로써 생기는 Dirty Read 문제가 발생할 수 있다.


Dirty Read

Dirty Read란, 한 트랜잭션의 다른 트랜잭션의 커밋되지 않은 변경사항을 조회하는 것을 말한다. Dirty Read로 인해 어떤 문제가 발생하는지 아래 그림과 함께 자세히 설명한다.

image
  • 쓰기 트랜잭션 T2는 원래 30이었던 age값을 20으로 변경한다.
  • 읽기 트랜잭션 T1은 커밋되지 않은 age값인 20을 읽어들인다. 👉 Dirty Read 발생
  • 어떤 이유로 T2에서 롤백이 일어나서, T2의 변경 작업이 무효가 된다.(age가 30으로 복구)
  • T1은 틀린 데이터인 age=20 을 갖고 작업을 계속한다. 👉 문제 발생

이러한 Dirty Read 문제는 트랜잭션 격리 수준을 Read Committed로 격상함으로써 해결할 수 있다.


Read Committed (Level 1)

Read Committed는 트랜잭션의 변경 작업을 외부에서 조회할 수 없게 하는 격리 수준이다. 즉, 커밋이 완료된 데이터만 조회 가능하므로, Dirty Read 문제를 해결할 수 있다. 예시를 통해 Read Committed가 어떻게 Dirty Read를 해결하는지 살펴보자.

image
  • 쓰기 트랜잭션 T2는 원래 30이었던 age값을 20으로 변경한다.
  • 읽기 트랜잭션 T1이 age를 조회할 때, 이전에 커밋이 완료되었던 값인 30을 조회한다.
  • T1은 옳은 데이터(age=30)을 가지고 작업을 계속한다. 👉 Dirty Read 문제 해결

Read Committed에서는 커밋이 되지 않은 값을 읽지 않는다. 따라서 틀린 데이터를 사용할 일이 없게 되므로 Dirty Read 문제를 해결할 수 있게 된다.

그러나, Read Committed 격리 수준에서는 Non-Repeatable Read 문제가 발생할 수 있다.


Non-Repeatable Read

Non-Repeatable Read란 처음 데이터를 조회 후 다시 데이터를 조회 시, 첫 조회 결과와 다른 조회 결과를 얻는 현상을 말한다. 그림을 통해 Non-Repeatable Read의 발생 원인을 알아보자.

image
  • 읽기 트랜잭션 T1이 age를 조회한다. 조회 결과: age=30
  • 쓰기 트랜잭션 T2가 age를 20으로 변경하고, 커밋한다.
  • T1이 age를 다시 조회한다. 조회 결과: age=20
    • 👉 이전 조회 시와 결과가 다른 Non-Repeatable Read 발생

T1이 두 번째로 age를 읽었을 때, 커밋된 값을 읽은 것이므로 틀린 데이터를 읽은 것은 아니다. 다만 한 트랜잭션(그림의 경우 T1에 해당)에서 같은 SQL에 대한 결과가 다른 것 자체가 문제될 수 있다. 예를 들어, 입금과 출금이 지속되는 경우 잔액을 조회하는 SELECT 쿼리가 실행될 때마다 다른 조회 결과를 가져오는 문제를 야기할 수 있다.

이러한 Non-Repeatable Read 문제는 격리 수준을 Repeatable Read으로 격상함으로써 해결할 수 있다.

Repeatable Read (Level 2)

Repeatable Read는 같은 트랜잭션 내에서 조회한 데이터의 값이 항상 동일함을 보장하는 격리 수준이다. MySQL의 경우 해당 격리 수준을 기본 격리 수준으로 사용하며, 최초 트랜잭션 SELECT 실행 시 스냅샷을 만들어두고, 이후 해당 스냅샷에 SELECT를 실행함으로써 다른 트랜잭션에서 데이터가 변경되더라도 조회에 대해 같은 결과를 보장한다.

REPEATABLE READ

"This is the default isolation level for InnoDB. Consistent reads within the same transaction read the snapshot established by the first read." - MySQL 8.0 Reference Manual

image

트랜잭션 격리 수준이 Read Committed일 때는 처음 age값을 조회 후 커밋된 age값을 다시 조회하여 Non-Repeatable Read 문제가 발생하였다면, Repeatable Read 수준에서는 다른 트랜잭션의 커밋 여부와 상관없이 데이터를 조회 시 처음 조회한 값과 동일한 결과를 리턴하므로 Non-Repeatable Read 문제가 해결된다.

그러나, Repeatable 격리 수준에서는 Phantom Read 문제가 발생할 수 있다.

Phantom Read

Phantom Read란, 처음 데이터를 조회 후 다시 데이터를 조회할 때 이전에 없던 데이터가 조회되는 문제를 말한다. 그림을 통해 Phantom Read의 발생 원인을 알아보자.

image
  • 읽기 트랜잭션 T1이 데이터를 읽는다. 조회된 Row는 1개이다.
  • 쓰기 트랜잭션 T2가 데이터를 삽입한다.
  • 읽기 트랜잭션 T1이 데이터를 읽는다. 조회된 Row는 2개이다.
    • 👉 이전 조회 시 없던 데이터가 조회되는 Phantom Read 발생

트랜잭션이 읽기 작업을 반복하는 중간에, 다른 트랜잭션이 데이터를 삽입함으로써 이전에 없던 데이터가 조회된다. Phantom Read는 격리 수준을 Serializable로 격상함으로써 해결할 수 있다.


Serializable

Serializable은 트랜잭션의 읽기 작업 시에도 공유락을 획득하게 함으로써, 동시에 다른 트랜잭션이 같은 데이터에 접근할 수 없도록 하는 격리 레벨이다. 읽기 트랜잭션이 데이터를 사용하는 도중에 다른 트랜잭션이 INSERT를 수행할 수 없게 되므로 Phantom Read 문제를 해결할 수 있다.

image

Serializable의 경우 격리 수준 중 제한이 가장 심하므로 데이터의 동시성 또한 제일 낮다. 따라서 필요성을 검증하고 사용하는 편이 좋다.

마치며

요약하자면, 트랜잭션 격리 수준은 여러 트랜잭션이 같은 데이터(또는 테이블)에 접근하여 생기는 문제들(Dirty Read, Non-Repeatable Read, Phantom Read)을 해결하기 위해 트랜잭션간 조회할 수 있는 데이터의 범위를 결정하는 것이라고 볼 수 있다.

격리 수준을 적절하게 조절함으로써, 트랜잭션 동시 실행 문제(Dirty Read, Non-Repeatable Read, Phantom Read)를 해결할 수 있으며, 격리 수준을 높임으로써 얻는 데이터 일관성과 동시성은 반비례하기 때문에 필요성을 따져 선택하는 것이 중요하다.

마침.


더 공부해볼 것

  • MySQL MVCC

📝 Reference


잘못된 내용이 있을 경우 언제든지 댓글로 남겨주시면 감사하겠습니다! 🙇‍♂️

profile
Every Run, Learn Counts.

1개의 댓글

comment-user-thumbnail
2023년 5월 31일

innoDB 엔진이 왜 repetable read를 채택했는지 이해할 수 있어 좋았습니다!
다이어그램 덕분에 쉽게 이해하고 갑니다~

답글 달기