Database Isolation
Database isolation은 동시에 실행되는 transaction이 서로의 중간 상태와 재조회 결과를 어디까지 볼 수 있는지 정하는 consistency boundary다.
Spring Transaction이 하나의 작업을 commit 또는 rollback 단위로 묶는다면, isolation은 그 경계 안에서 여러 transaction 사이의 visibility를 정한다. Propagation이 "nested call이 transaction을 공유할지"를 결정하는 것과 달리, isolation은 "concurrent read/write anomaly를 어느 수준까지 허용할지"를 결정한다.
동시성이 만드는 읽기 이상 현상
- Dirty read: 아직 commit되지 않은 값을 읽는 현상.[1] 이후 그 transaction이 rollback되면 존재하지 않았던 값을 읽은 셈이 된다(해석).
- Non-repeatable read: 같은 row를 두 번 조회할 때, 중간에 다른 transaction이 값을 수정·commit해 값이 달라지는 현상.[1:1] transaction 내부 판단 기준이 흔들린다.
- Phantom read: 같은 범위 쿼리를 두 번 조회할 때, 중간에 다른 transaction이 insert/delete해 결과 row 집합이 달라지는 현상.[1:2] 범위 기반 검증·집계 결과가 달라진다.
sequenceDiagram
participant T1 as Transaction 1
participant DB as Database
participant T2 as Transaction 2
T1->>DB: 범위 또는 row 조회
T2->>DB: update, insert, commit
T1->>DB: 같은 조회 재실행
DB-->>T1: isolation level에 따른 visibility격리 수준 선택지
- Transaction source는
READ_UNCOMMITTED,READ_COMMITTED,REPEATABLE_READ,SERIALIZABLE을 격리 수준 선택지로 제시한다.[2] - source는 dirty, non-repeatable, phantom read를 서로 다른 concurrent anomaly로 구분한다.[1:3]
- isolation을 높일수록 동시성 비용과 lock 또는 retry 부담이 커질 수 있다고 판단한다 — source의 "성능 저하가 심해 특수한 금융 거래 등 외에는 거의 미사용"이라는
SERIALIZABLE설명에서 이어지는 해석이다.[2:1]
선택 기준
이 순서로 좁혀 가는 편이 실용적이라고 판단한다 — source가 표를 제공할 뿐 선택 절차 자체를 제시하지는 않으므로, 아래는 이 페이지의 해석이다.
- 다른 transaction의 미완료 변경을 절대 읽으면 안 된다면
READ_COMMITTED이상이 기본선이다. - 같은 transaction에서 같은 entity 값이 판단 근거라면 repeatable visibility가 필요한지 검토한다.
- 재고, 예약, 정산처럼 범위 query 결과가 business invariant에 직접 연결되면 phantom과 write skew를 DBMS 동작까지 포함해 검증한다.
- 격리 수준만 높여 business rule을 해결하려 하지 말고, unique constraint·optimistic lock·conditional update 같은 명시적 invariant도 함께 설계한다.
- 예를 들어 READ COMMITTED에서 일반 SELECT는 커밋된 데이터만 보므로, 거의 동시에 들어온 두 요청이 둘 다 "없음"을 보고 둘 다 INSERT할 수 있다.[3]
- 트랜잭션은 "다 되거나 다 안 되거나"를 보장할 뿐 다른 요청이 사이에 끼어드는 것을 막지 않으며, 이 틈은 커밋되지 않은 행까지 아는 유니크 인덱스가 막는다.[3:1] 구체적인 방법은 Unique Constraint Duplicate Prevention에 정리했다.
표준 isolation 이름이 같아도 lock, MVCC snapshot, phantom 처리 방식은 DBMS와 설정에 따라 다르다. 이 페이지는 개념 선택 기준이며 특정 DBMS의 구현 보장이 아니다.
관련
- Spring Transaction
- Transaction Propagation
- Audit Log
- Unique Constraint Duplicate Prevention — 격리 수준으로 막을 수 없는 동시 중복 INSERT를 유니크 인덱스로 막는 패턴.
- Database Normalization — 같은 "이상 현상"이라는 말을 쓰지만, 이 page는 동시 실행의 읽기 이상을, 정규화는 설계 결함의 입력·수정·삭제 이상을 다룬다.
출처
테스트 질문
- Propagation과 isolation은 각각 무엇을 제어하는가?
- Dirty, non-repeatable, phantom read는 어떤 visibility 차이에서 생기는가?
- 높은 isolation만으로 business invariant를 보장할 수 없는 이유는 무엇인가?
- READ COMMITTED에서 조회 후 INSERT하는 두 요청이 모두 "없음"을 보는 이유는 무엇인가?
Transaction.md — "Dirty Read: 다른 트랜잭션이 커밋하지 않은 변경 단계의 데이터를 읽어 발생하는 일시적 왜곡 현상.", "Non-Repeatable Read: 한 트랜잭션 안에서 같은 데이터를 두 번 조회할 때, 중간에 다른 트랜잭션이 값을 수정/커밋하여 두 조회 값이 다르게 나타나는 현상.", "Phantom Read: 한 트랜잭션 안에서 범위 쿼리를 두 번 조회할 때, 중간에 다른 트랜잭션이 데이터를 삽입/삭제하여 결과 행(Row) 수가 달라지는 현상." ↩︎ ↩︎ ↩︎ ↩︎
Transaction.md — 격리 수준 표: "READ_UNCOMMITTED... 데이터 부정확성이 매우 높아 실무 미사용.", "READ_COMMITTED... Oracle, PostgreSQL 등 다수 RDBMS의 기본값.", "REPEATABLE_READ... MySQL(InnoDB)의 기본값.", "SERIALIZABLE... 성능 저하가 심해 특수한 금융 거래 등 외에는 거의 미사용." ↩︎ ↩︎
인증 실패 응답·좋아요 중복 방지 노트 — "트랜잭션이 보장하는 것은 "내 작업이 다 되거나 다 안 되거나"다. "다른 요청이 사이에 끼어들지 못하게"는 보장하지 않는다.", "기본 격리 수준(READ COMMITTED)에서 일반 SELECT는 커밋된 데이터만 본다. 그래서 둘 다 "없음"을 본다.", "일반 조회와 달리 유니크 인덱스는 커밋 안 된 행까지 안다." ↩︎ ↩︎