트랜젝션(Transaction) 격리 수준(Isolation Level)
Series: 데이터베이스
들어가며
데이터베이스를 다루다 보면 한 번쯤 이런 상황을 마주하게 된다.
왜 같은 쿼리를 두 번 실행했는데 결과가 다르지?
아직 커밋도 안 된 데이터가 조회되는 게 맞는 건가?
이러한 문제는 대부분 트랜잭션과 격리 수준(Isolation Level) 과 관련이 있다. 트랜잭션은 데이터의 일관성을 보장하기 위한 중요한 개념이지만, 동시에 여러 트랜잭션이 실행될 때는 예상하지 못한 문제가 발생할 수 있다. 이를 제어하기 위해 존재하는 것이 바로 트랜잭션 격리 수준이다. 이번 글에서는 트랜잭션 격리 수준이 왜 필요한지, 어떤 문제를 해결하기 위한 개념인지, 그리고 각 수준의 차이에 대해 정리해보겠다.
1. 개요
1.1. 트랜잭션이란?
트랜잭션(Transaction)은 데이터베이스에서 하나의 작업 단위를 의미한다. 예를 들어 계좌 이체를 생각해보자.
- A 계좌에서 돈을 차감
- B 계좌에 돈을 추가
이 두 작업은 반드시 함께 성공하거나, 함께 실패해야 한다. 중간에 하나만 실행된다면 데이터는 깨지게 된다. 이처럼 트랜잭션은 다음과 같은 특성을 가진다.
- Atomicity(원자성)
- Consistency(일관성)
- Isolation(격리성)
- Durability(지속성)
이 중에서 오늘 다룰 내용은 바로 격리성(Isolation) 이다.
1.2. 격리성이 필요한 이유
트랜잭션이 하나만 실행된다면 아무 문제가 없다. 하지만 실제 서비스에서는 수많은 트랜잭션이 동시에 실행된다. 이때 서로 간섭이 발생하면 다양한 문제가 생긴다. 예를 들어, 아직 커밋되지 않은 데이터를 읽는 경우, 같은 데이터를 두 번 읽었는데 결과가 다른 경우, 조회 시마다 결과 row 수가 달라지는 경우 등이 있다. 이러한 문제들을 제어하기 위해 트랜잭션 격리 수준이 존재한다.
2. 격리 수준과 발생 가능한 문제
트랜잭션 격리 수준을 이해하려면, 먼저 어떤 문제가 발생할 수 있는지를 아는 것이 중요하다.
2.1. Dirty Read
Dirty Read는 아직 커밋되지 않은 데이터를 읽는 경우를 의미한다.
예를 들어,
- 트랜잭션 A가 데이터를 수정 (아직 commit 안 함)
- 트랜잭션 B가 해당 데이터를 읽음
- 트랜잭션 A가 rollback
이 경우 B는 존재하지 않는 데이터를 읽은 셈이 된다.
2.2. Non-Repeatable Read
Non-Repeatable Read는 같은 데이터를 두 번 읽었는데 값이 달라지는 경우이다.
예를 들어,
- 트랜잭션 A가 데이터를 조회 (값: 100)
- 트랜잭션 B가 데이터를 수정 후 commit
- 트랜잭션 A가 다시 조회 (값: 200)
같은 트랜잭션 내에서 결과가 달라지는 문제가 발생한다.
2.3. Phantom Read
Phantom Read는 조회 결과의 row 수가 달라지는 경우이다.
예를 들어,
- 트랜잭션 A가 조건에 맞는 데이터 조회 (10건)
- 트랜잭션 B가 새로운 데이터 insert 후 commit
- 트랜잭션 A가 다시 조회 (11건)
이 경우 마치 유령(Phantom)이 나타난 것처럼 결과가 달라진다고 해서 Phantom Read라는 명칭이 붙었다.
3. 트랜잭션 격리 수준
트랜잭션 격리 수준은 이러한 문제들을 어디까지 허용할 것인지에 따라 나뉜다.
3.1. READ UNCOMMITTED
가장 낮은 격리 수준이다.
- Dirty Read 허용
- Non-Repeatable Read 발생 가능
- Phantom Read 발생 가능
말 그대로 커밋되지 않은 데이터도 읽을 수 있으며 실무에서는 거의 사용하지 않는다.
3.2. READ COMMITTED
커밋된 데이터만 읽을 수 있는 수준이다.
- Dirty Read 방지
- Non-Repeatable Read 발생 가능
- Phantom Read 발생 가능
대부분의 RDBMS에서 기본값으로 사용되는 경우가 많다. 트랜잭션 간의 간섭을 어느 정도 허용하면서도, 최소한의 일관성을 보장한다.
3.3. REPEATABLE READ
같은 트랜잭션 내에서는 동일한 결과를 보장하는 수준이다.
- Dirty Read 방지
- Non-Repeatable Read 방지
- Phantom Read 발생 가능 (이론적으로)
MySQL(InnoDB)의 기본 격리 수준이며, 같은 데이터를 여러 번 읽어도 값이 변하지 않는다.
3.4. SERIALIZABLE
가장 높은 격리 수준이다.
- Dirty Read 방지
- Non-Repeatable Read 방지
- Phantom Read 방지
트랜잭션을 순차적으로 실행하는 것과 동일한 효과를 낸다. 하지만 그만큼 성능 저하가 크기 때문에, 일반적인 서비스에서는 잘 사용하지 않는다.
4. 개인적 견해
트랜잭션 격리 수준을 처음 접하면 '무조건 높은 수준이 좋은 것 아닌가?'라는 생각이 들 수 있다. 하지만 실제로는 그렇지 않다. 격리 수준이 높아질수록 데이터 일관성은 보장되지만, 그만큼 동시성은 떨어진다. 즉, 성능과 직결된다. 예를 들어 SERIALIZABLE 수준을 사용하면 가장 안전하지만, 사실상 모든 트랜잭션이 순차적으로 실행되는 것과 유사하기 때문에 처리량이 크게 감소할 수 있다.
그래서 실무에서는 대부분 다음과 같은 기준을 사용한다.
- 일반적인 조회/수정 → READ COMMITTED
- 데이터 정합성이 중요한 경우 → REPEATABLE READ
- 매우 중요한 금융/정산 로직 → 필요 시 SERIALIZABLE
결국 중요한 것은 어떤 격리 수준이 더 좋은가?가 아니라, 현재 비즈니스 요구사항에 어떤 수준이 적절한가?이다.
마치며
트랜잭션 격리 수준은 데이터베이스의 동시성 제어를 이해하는 데 있어 핵심적인 개념이다. 단순히 개념을 외우는 것보다 각 격리 수준이 어떤 문제를 허용하고, 어떤 문제를 막는지 이해하는 것이 중요하다. 실무에서는 성능과 정합성 사이에서 적절한 균형을 잡는 것이 중요하다. 결국 좋은 시스템은 모든 문제를 완벽하게 막는 시스템이 아니라, 필요한 만큼만 제어하면서도 충분히 빠르게 동작하는 시스템이라고 생각한다.