트랜잭션 격리 수준(Transaction Isolation Level)
1. 개요
가. 정의
동시에 실행되는 여러 트랜잭션이 서로에게 어느 정도까지 영향을 미치도록 허용하는지(격리 정도) 를 규정한 수준. ACID의 격리성(Isolation) 을 실무적으로 구현하며, 동시성과 일관성 사이의 트레이드오프를 조절하는 다이얼이다.
이상적으로는 모든 트랜잭션이 마치 혼자 실행되는 것처럼 완벽히 격리되어야 하지만(Serializable), 그렇게 하면 동시 처리량이 급락한다. 그래서 표준(ANSI SQL)은 완벽한 격리 대신 일부 이상현상을 감수하는 대가로 동시성을 얻는 여러 단계를 정의했다. 격리 수준을 이해한다는 것은 곧 "어떤 이상현상을 허용하고 어떤 성능을 살 것인가"를 판단하는 것이다.
나. 등장 배경 및 필요성
격리를 강화하면 일관성은 올라가지만 락 경합이 늘어 동시성이 떨어지고, 완화하면 동시성은 오르지만 이상현상 위험이 커진다. 즉 격리↑ = 일관성↑·동시성↓ 라는 반비례가 존재한다. 모든 업무에 최고 격리를 강제하면 성능이 나오지 않고, 반대로 낮은 격리를 무분별하게 쓰면 데이터 정합성이 깨진다. 따라서 업무의 성격—돈이 오가 정합성이 절대적인지, 통계 조회라 약간의 부정확을 감수해도 되는지—에 맞춰 적정 수준을 선택하는 것이 필요하다.
2. 격리 수준별 이상현상
격리 수준은 세 가지 대표 이상현상을 낮은 수준에서부터 하나씩 차단하는 방식으로 계단을 이룬다. 아래로 갈수록 더 많은 이상현상을 막는 대신 동시성은 낮아진다.
| 격리 수준 | Dirty Read | Non-repeatable Read | Phantom Read |
|---|---|---|---|
| Read Uncommitted | 발생 | 발생 | 발생 |
| Read Committed | 방지 | 발생 | 발생 |
| Repeatable Read | 방지 | 방지 | 발생(가능) |
| Serializable | 방지 | 방지 | 방지 |
flowchart LR
A[Read Uncommitted<br/>격리 최저] --> B[Read Committed] --> C[Repeatable Read] --> D[Serializable<br/>격리 최고]
3. 이상현상 사례
세 이상현상은 각각 "미확정 데이터를 읽었는가 / 같은 것을 두 번 읽었을 때 값이 바뀌었는가 / 건수가 바뀌었는가" 로 구분된다. 사례로 보면 원리가 명확해진다.
Dirty Read(오손 읽기) 는 아직 커밋되지 않은 변경을 읽는 것이다. A가 잔액을 100→200으로 수정(미커밋)한 것을 B가 200으로 조회했는데, 이후 A가 롤백하면 B는 존재한 적 없는 값을 읽은 셈이 된다. Read Committed부터는 커밋된 데이터만 읽어 이를 막는다.
Non-repeatable Read(반복 불가능 읽기) 는 한 트랜잭션 안에서 같은 행을 두 번 읽었는데 값이 다른 것이다. B가 어떤 행을 읽고 다시 읽는 사이에 A가 그 행을 변경·커밋하면, B는 같은 질의에 다른 답을 얻는다. Repeatable Read는 트랜잭션 동안 읽은 행의 스냅샷을 유지해 이를 막는다.
Phantom Read(유령 읽기) 는 범위 조회의 건수가 달라지는 것이다. B가 "잔액 100 이상 계좌"를 조회하고 다시 조회하는 사이 A가 조건에 맞는 새 행을 삽입·커밋하면 없던 행이 나타난다. 이는 개별 행이 아니라 범위에 대한 것이라 Serializable(또는 범위 락/Next-key Lock)에서 완전히 막힌다.
| 수준 | 사례 |
|---|---|
| Read Uncommitted | A가 잔액 100→200 미커밋, B가 200 조회 → A 롤백 시 Dirty Read |
| Read Committed | B 조회 중 A 변경·커밋 → 재조회 시 값 상이(Non-repeatable) |
| Repeatable Read | 같은 행은 일관하나 범위 조회 중 새 행 삽입 시 Phantom |
| Serializable | 완전 직렬화, 모든 이상현상 방지, 동시성 최저 |
4. 동시성 제어 기법
격리 수준은 어떻게 구현하느냐에 따라 성능이 크게 달라진다. 전통적 Lock 기반은 읽기·쓰기에 공유·배타 락을 걸고 2단계 잠금(2PL)으로 직렬성을 보장하지만, 읽기가 쓰기를 막아 경합이 크다. 이 한계를 넘기 위해 현대 DBMS는 MVCC(다중 버전 동시성 제어) 를 쓴다.
| 기법 | 내용 |
|---|---|
| Lock 기반 | 공유·배타 락, 2PL(2단계 잠금) |
| MVCC | 스냅샷 버전으로 읽기-쓰기 충돌 최소화(Oracle·PostgreSQL) |
| Snapshot Isolation | 트랜잭션 시작 시점 스냅샷을 읽어 일관성 확보 |
MVCC가 중요한 이유는 데이터를 덮어쓰지 않고 여러 버전으로 유지해, 읽는 쪽은 자기 시점의 스냅샷을 보고 쓰는 쪽은 새 버전을 만들게 함으로써 "읽기가 쓰기를 막지 않는다(readers don't block writers)" 를 달성하기 때문이다. 덕분에 높은 일관성과 높은 동시성을 동시에 얻을 수 있다.
5. 고려사항 및 시사점
실무에서 주의할 점은 DBMS마다 기본값과 실제 동작이 다르다는 것이다. Oracle·PostgreSQL의 기본값은 Read Committed이고, MySQL InnoDB는 Repeatable Read이며, 특히 InnoDB는 Next-key Lock으로 Repeatable Read에서도 Phantom을 상당 부분 막는다. 따라서 표준의 이론표만 믿지 말고 사용하는 엔진의 실제 구현을 확인해야 한다. 선택 전략은 명확하다. 금융 결제처럼 정합성이 절대적이면 Serializable(또는 명시적 락)을, 조회·통계 위주라면 낮은 수준으로 동시성을 살린다. 격리를 낮춘 경우에는 애플리케이션 레벨에서 낙관적 락(버전 컬럼 검증) 등으로 정합성을 보완해, 성능과 일관성의 균형을 맞추는 것이 기술사적 판단이다.
한 줄 요약: 격리 수준은 Read Uncommitted→Committed→Repeatable Read→Serializable 로 갈수록 Dirty·Non-repeatable·Phantom Read를 차례로 차단하되 동시성을 희생하며, Lock·MVCC로 구현하고 업무 정합성 요구에 맞춰 수준을 골라 일관성과 성능의 균형을 잡는다.