1. 트랜잭션 격리 수준(Isolation Level) 이란?
🧱 트랜잭션(Transaction)과 격리 수준(Isolation)의 개념
- 
트랜잭션(Transaction): 데이터베이스에서 하나의 작업 단위를 의미하며, 다음의 4가지 특성(ACID)을 만족해야 합니다. 
- Atomicity (원자성): 모두 수행되거나 모두 수행되지 않아야 함
- Consistency (일관성): 트랜잭션 완료 후에도 데이터는 항상 유효한 상태
- Isolation (격리성): 각 트랜잭션은 서로 간섭하지 않아야 함
- Durability (지속성): 일단 완료된 트랜잭션은 시스템 장애에도 유지됨
 
- 
Isolation Level은 여러 트랜잭션이 동시에 실행될 때, 서로의 작업이 어느 정도까지 보장되며 간섭이 허용되는가를 정의합니다. 
🕸️ 왜 격리 수준이 필요한가? (동시성 문제)
트랜잭션이 격리되지 않으면 다음과 같은 데이터 정합성 문제가 발생할 수 있습니다:
- Dirty Read: 아직 커밋되지 않은 데이터를 읽는 문제
- Non-Repeatable Read: 같은 쿼리를 반복해도 결과가 바뀌는 문제
- Phantom Read: 조건에 맞는 새로운 행이 삽입되거나 삭제되는 문제
이러한 문제를 방지하기 위한 것이 격리 수준이며, 그 강도에 따라 위 문제들을 방지할 수 있는 범위가 달라집니다.
2. 4가지 Isolation Level 비교 요약
🔹 1) READ UNCOMMITTED
- 가장 낮은 격리 수준
- 다른 트랜잭션이 커밋하지 않은 변경사항도 읽을 수 있음 (Dirty Read 허용)
- 빠르지만 정합성 보장이 거의 없음
🔹 2) READ COMMITTED
- 대부분의 DBMS에서 기본 설정 (MySQL은 아님)
- 커밋된 데이터만 읽을 수 있음 → Dirty Read 방지
- 하지만 Non-Repeatable Read는 발생 가능
🔹 3) REPEATABLE READ
- MySQL의 기본 격리 수준
- 동일한 쿼리에 대해 항상 같은 결과를 반환 → Non-Repeatable Read 방지
- 단, Phantom Read는 방지하지 못함
🔹 4) SERIALIZABLE
- 가장 높은 격리 수준
- 모든 트랜잭션을 순차적으로 처리한 것처럼 보이도록 보장
- 모든 읽기 문제를 방지하지만, 성능 저하가 심함
📌 ANSI SQL 표준 vs MySQL의 차이점
- ANSI SQL 표준에서는 REPEATABLE READ가 Phantom Read까지 막는다고 정의되어 있다.
- 그러나 MySQL의 InnoDB 스토리지 엔진에서는 Phantom Read가 발생할 수 있다
- 이유: 내부적으로 MVCC로 구현되어 있으며, 범위 조건 INSERT/DELETE는 막지 않는다
 
즉, MySQL의 REPEATABLE READ는 이름과 달리 완전한 격리 수준이 아님에 주의해야 합니다.
3. 격리 수준만으로 부족한 이유: 읽기 vs 쓰기 관점에서의 한계
📖 읽기 관점에서 살펴보는 Isolation Level
격리 수준은 주로 읽기 충돌을 방지하기 위해 도입되었으며, 다음과 같은 문제가 발생할 수 있습니다:
👓 발생 가능한 읽기 문제 요약
| 문제 유형 | 설명 | 발생 조건 | 
|---|
| Dirty Read | 다른 트랜잭션의 미커밋 데이터를 읽음 | READ UNCOMMITTED | 
| Non-Repeatable Read | 같은 SELECT가 다른 결과를 반환 | READ COMMITTED | 
| Phantom Read | WHERE 조건에 해당하는 row가 뒤에서 추가됨 | REPEATABLE READ (in MySQL) | 
📊 어떤 Isolation Level이 어떤 문제를 막아주는가?
| 격리 수준 | Dirty Read | Non-Repeatable Read | Phantom Read | 
|---|
| READ UNCOMMITTED | 발생함 | 발생함 | 발생함 | 
| READ COMMITTED | 방지됨 | 발생함 | 발생함 | 
| REPEATABLE READ | 방지됨 | 방지됨 | 발생 가능 (MySQL 기준) | 
| SERIALIZABLE | 방지됨 | 방지됨 | 방지됨 | 
💡 읽기 충돌만 본다면 SERIALIZABLE이 유일하게 모든 문제를 막지만, 성능 측면에서는 실무 적용이 어렵습니다.
쓰기 관점에서 살펴보는 Isolation Level
읽기 외에도 더 치명적인 문제가 발생하는 영역이 쓰기 충돌입니다.
🔥 쓰기 충돌의 3가지 주요 유형
| 유형 | 설명 | 발생 시점 | 
|---|
| Write Skew | 서로 다른 조건을 보고 동시에 쓰기를 시도 → 정합성 깨짐 | 읽기 후 쓰기 사이 | 
| Lost Update | 마지막 트랜잭션이 이전 값을 덮어씀 | 동시에 같은 row를 수정 | 
| Write-Write Conflict | 동일 데이터를 동시에 수정하려는 시도 | 비관적 락이나 SERIALIZABLE 없으면 발생 | 
❓ 격리 수준만으로 충분한가?
절대 아닙니다.
- READ COMMITTED, REPEATABLE READ 모두 쓰기 충돌을 자동으로 감지하거나 막지 못함
- 특히 REPEATABLE READ는 읽기는 보호하지만, 쓰기 간 충돌은 허용함 → 대표적인 예: Write Skew
그래서 실무에서는 락 (비관적/낙관적) 전략이 필요합니다.
4. MySQL의 기본 격리 수준: Repeatable Read의 한계
MySQL은 기본적으로 Repeatable Read (RR) 를 사용하며, 이는 다음과 같은 특성을 가집니다:
✅ 장점: 일관된 읽기 제공 (Consistent Read)
- 트랜잭션 시작 시점의 Snapshot을 사용하여 항상 동일한 값을 읽음
- Non-Repeatable Read는 방지됨
❌ 단점: 쓰기 충돌 감지는 불가
- RR은 읽기 시점만 보장 → 쓰기에는 개입하지 않음
- 트랜잭션이 동일한 값을 보고 서로 다른 쓰기를 해도 이를 감지 못함
→ 결국 Write Skew 문제가 발생할 수 있는 구조
5. 실무 사례: 입금/잔고 시스템에서 발생하는 문제
💸 문제 시나리오
- 사용자 A와 B가 동시에 계좌 잔고를 확인 (예: balance = 10000원)
- 각각 5000원을 입금하기로 결정
- 둘 다 balance += 5000수행
- 최종 잔고는 15000원이 되어야 함
하지만 실제로는 마지막 트랜잭션만 반영되어 10000 → 15000 또는 10000 → 20000이 됨
→ 이게 바로 Lost Update or Write Skew 현상
🧨 정합성 손상의 결과
- 실제 입금은 잘 됐다고 사용자에게 보여졌지만, DB에는 반영되지 않음
- 충돌 자체를 감지하지 못하므로, 오류 로그도 남지 않음
- 이로 인해 사용자 A 또는 B가 돈을 입금했지만 사라진 것처럼 보이게 됨
📌 MySQL Repeatable Read는 읽기 일관성은 지켜주지만, 쓰기 정합성까지는 보장하지 않음
📌 그 이유는 MySQL이 MVCC 기반으로 동작하면서, 트랜잭션 시작 시점의 Snapshot만을 기준으로 읽기 정합성만 보장하기 때문입니다.
쓰기 시점에 데이터가 변경됐는지는 검사하지 않기 때문에, 동일 데이터를 기반으로 한 동시 쓰기 충돌을 감지하지 못하며,
이로 인해 balance 같은 누적 연산에서는 정합성 오류(Lost Update, Write Skew) 가 발생할 수 있습니다.
따라서 안정적인 처리를 위해서는 낙관적 락(Optimistic Locking) 또는 비관적 락(Pessimistic Locking) 을 추가 적용해야 합니다.
6. 실무에서 쓰기 충돌을 다루는 3가지 전략
단순한 락이 아니다 — 정합성과 신뢰를 수호하는 실전 기술
🔐 ① 비관적 락 (Pessimistic Locking: SELECT ... FOR UPDATE)
- 트랜잭션 내에서 데이터를 읽을 때 다른 트랜잭션이 해당 row를 수정하지 못하도록 잠금
- 가장 강력하고 확실한 방식
- 단점: Deadlock 가능성, 높은 경쟁 시 성능 저하
SELECT balance FROM account WHERE id = 1 FOR UPDATE;
트랜잭션 커밋 전까지 다른 트랜잭션은 이 row에 접근 불가
🧱 ② 테이블 락 (Table Lock)
- 테이블 전체를 잠그는 방식
- 단점: 병렬성 완전 손실, 거의 쓰이지 않음
- 보통 DDL 수행 시 내부적으로 사용됨
LOCK TABLE account WRITE;
✨ ③ 낙관적 락 (Optimistic Locking using version)
- 실제 충돌이 일어날 거라고 낙관적으로 가정
- 데이터를 업데이트할 때 버전(version)이나 수정 시점(timestamp) 을 조건으로 넣어 충돌 감지
UPDATE account
SET balance = balance + 5000, version = version + 1
WHERE id = 1 AND version = 5;
충돌 시 업데이트 실패 → 이를 감지하고 재시도할 수 있음
7. 낙관적 락이 실무에서 표준이 된 이유
✅ 가장 가볍고 유연한 락 전략
- DB 내부의 락이 아님 → 애플리케이션 수준의 충돌 감지
- 락 경합 없음 → 대량의 동시 요청도 병렬로 처리 가능
- 충돌이 거의 없는 환경 (예: 잔고 조회, 알림 설정 등)에 최적화
🔁 충돌이 감지되면 어떻게 되는가?
- 업데이트가 실패하면, 이를 애플리케이션에서 감지 가능
- 실패 로직을 재시도하거나 사용자에게 알림으로 처리 가능
if (updateResult == 0) {
    
}
📈 확장성과 정합성 사이의 완벽한 균형
- 무거운 락 없이도 정합성 보장
- MSA, CQRS, 이벤트 소싱 같은 분산 시스템에서도 효과적으로 사용 가능
8. 낙관적 락은 기술이 아닌 비즈니스 정합성 수호 전략
🧭 "락을 건다"는 건 기술이 아니라 의지다
- 잔고, 포인트, 재고 같은 값은 '절대 틀리면 안 되는 값'
- 낙관적 락은 그 값을 절대 틀리지 않게 하겠다는 비즈니스 의지의 표현
🧱 클린 아키텍처에서의 위치
- 낙관적 락은 도메인 계층의 불변 조건을 지키기 위한 도구
- DB나 기술 스택에 의존하는 것이 아니라, 비즈니스 규칙으로써 존재
즉, 낙관적 락은 인프라가 아니라 도메인의 책임
🔄 예시: 계좌 이체 시스템
- 동시에 2명의 사용자가 같은 잔고에 접근할 수 있음
- 낙관적 락으로 충돌을 감지하고 막음 → 사용자의 돈이 잘못 계산되지 않도록 보장
💡 낙관적 락은 단순한 성능 전략이 아닌, 정합성과 신뢰를 위한 최종 방어선입니다.
시스템이 커질수록, 낙관적 락을 어디에 걸 것인지를 도메인 중심으로 고민해야 합니다.
9. 각 DBMS별 기본 Isolation Level 정리
| DBMS | 기본 격리 수준 | 특이사항 | 
|---|
| MySQL (InnoDB) | Repeatable Read | MVCC 기반, Phantom Read 일부 허용 | 
| PostgreSQL | Read Committed | 가장 현실적인 기본값, 강력한 MVCC | 
| Oracle | Read Committed | Serializable은 Serializable Snapshot 방식으로 작동 | 
| SQL Server | Read Committed | Lock 기반 격리 방식, Snapshot 격리 옵션 필요 | 
🔎 특징 요약
- MySQL은 Repeatable Read를 기본으로 하지만, 실제로는 Phantom Read까지 막지 않음 (MVCC의 한계)
- PostgreSQL은 기본이 Read Committed이지만 MVCC로 매우 강력한 일관성 유지
- Oracle은 Serializable이어도 실제로는 Snapshot 기반 → 동시성 뛰어남
- SQL Server는 기본적으로 락 기반 → 성능 튜닝에 민감
📌 DBMS별 기본 정책이 다르기 때문에, 동일한 트랜잭션 로직이라도 DBMS에 따라 격리 수준 설정을 명시하는 것이 중요
📌 다음 이야기 예고
지금까지 우리는 트랜잭션 격리 수준과 실무에서 발생하는 쓰기 충돌 문제, 그리고 이를 해결하는 전략들에 대해 살펴보았습니다.
하지만 트랜잭션 레벨에서의 정합성 제어만으로는 부족할 수 있습니다.
👉 더 나아가면, 아키텍처 레벨에서 읽기와 쓰기를 분리하여 확장성과 정합성을 동시에 확보하는 전략으로 이어질 수 있습니다.
예를 들어, CQRS와 Redis를 활용한 분산 환경에서의 설계 방식이 그러한 사례 중 하나입니다.
이러한 주제는 트랜잭션 격리 수준의 한계를 넘어서서,
시스템 확장성과 고가용성을 고려해야 하는 환경에서 점점 중요해지고 있습니다.
기회가 된다면 다음 글에서는 아키텍처적 접근을 실전 사례와 함께 다뤄보겠습니다.