Databaseadvanced검토 2026.08

Transaction, Isolation Level과 MVCC

동시 Transaction이 볼 수 있는 Version과 Lock 범위를 네 가지 격리 수준으로 이해한다.

#Transaction#Isolation Level#MVCC#Undo Log#Read View

Overview

Transaction은 여러 Database 연산을 하나의 논리 작업으로 묶는다. Isolation Level은 동시에 실행되는 다른 Transaction의 변경을 언제, 어떤 Version으로 볼 수 있는지 정한다. 격리를 높인다고 모든 동시성 문제가 자동으로 사라지는 것은 아니며, DBMS의 MVCC와 Lock 구현에 따라 실제 보장이 달라진다.

왜 필요한가

잔액 이체처럼 출금과 입금이 함께 성공해야 하는 작업, 재고 차감처럼 여러 요청이 같은 Row를 바꾸는 작업에는 중간 상태와 동시 변경을 통제할 경계가 필요하다. 격리가 약하면 처리량은 높아질 수 있지만 잘못된 중간 값을 읽을 수 있고, 강하면 대기와 Deadlock 비용이 커질 수 있다.

핵심 원리

ACID

  • Atomicity: 출금만 성공하고 입금이 실패하지 않도록 모두 Commit하거나 모두 Rollback한다.
  • Consistency: Transaction 전후에 잔액 음수 금지, FK, Unique 같은 불변식을 만족한다. 업무 불변식은 Application도 함께 책임진다.
  • Isolation: 동시 Transaction의 중간 상태가 정해진 규칙보다 많이 노출되지 않게 한다.
  • Durability: Commit 결과가 장애 후 Recovery에서도 유지된다.

대표적인 이상 현상

이상 현상의미간단한 예
Dirty ReadCommit되지 않은 값을 읽음T1이 바꾼 잔액을 T2가 읽었는데 T1이 Rollback
Non-repeatable Read같은 Row를 두 번 읽었는데 값이 달라짐T2가 중간에 Update 후 Commit
Phantom Read같은 조건의 Row 집합이 달라짐T2가 조건에 맞는 새 Row를 Insert

내부 동작

InnoDB는 Row의 이전 Version을 Undo 영역과 연결하고, Read View가 현재 Transaction에 보이는 Version을 판정한다. 일반 SELECT는 보통 Lock을 잡지 않는 Consistent Read를 사용한다. 변경할 Row를 읽는 SELECT ... FOR UPDATE는 최신 상태를 읽고 Lock을 획득하는 Locking Read다.

sequenceDiagram; participant T1 as Transaction 1; participant DB as InnoDB; participant T2 as Transaction 2; T1->>DB: 첫 Consistent Read / Read View; T2->>DB: UPDATE balance; T2->>DB: COMMIT; T1->>DB: 같은 Row 재조회; DB-->>T1: 격리 수준의 Read View에 맞는 Version

Undo Log와 Redo Log

Undo Log는 Rollback과 과거 Row Version 재구성에 사용된다. Redo Log는 Commit된 변경의 Crash Recovery와 Durability에 중요하다. READ UNCOMMITTED는 Redo Log를 읽고 READ COMMITTED는 Undo/Redo를 번갈아 읽는다고 외우면 안 된다. Isolation의 핵심은 Log 파일을 직접 선택하는 것이 아니라 Read View가 어떤 Record Version을 보이게 하는지다.

네 가지 Isolation Level

Level다른 Transaction의 미Commit 변경같은 Row 반복 조회같은 조건 반복 조회일반적인 비용
READ UNCOMMITTED보일 수 있음달라질 수 있음달라질 수 있음가장 약한 격리
READ COMMITTED보이지 않음Commit이 끼면 달라질 수 있음Row 집합이 달라질 수 있음Statement마다 최신 Commit 반영
REPEATABLE READ보이지 않음같은 Snapshot에서 유지DBMS 구현과 Read 방식에 따라 다름InnoDB 기본값, MVCC와 Range Lock 활용
SERIALIZABLE보이지 않음방지방지충돌 가능한 작업을 직렬화해 대기 증가

READ UNCOMMITTED

다른 Transaction이 아직 Commit하지 않은 변경을 읽을 수 있어 Dirty Read가 가능하다. Rollback될 값을 기반으로 다음 업무를 수행할 수 있으므로 일반적인 업무 시스템에서는 거의 선택하지 않는다.

READ COMMITTED

각 Consistent Read가 시작될 때 새로운 Read View를 사용하므로 Commit된 최신 값을 다음 조회에서 볼 수 있다. Dirty Read는 막지만 같은 Transaction에서 같은 Row를 다시 읽었을 때 값이 달라지는 Non-repeatable Read는 가능하다. Oracle과 PostgreSQL의 흔한 기본 격리 수준이지만 내부 구현까지 같다는 뜻은 아니다.

REPEATABLE READ

InnoDB에서는 Transaction의 첫 Consistent Read를 기준으로 만든 Snapshot을 이후 Consistent Read가 재사용한다. 다른 Transaction이 Commit해도 동일 Transaction의 일반 조회는 이전 Version을 계속 볼 수 있다. 다만 같은 Transaction이 직접 변경한 Row는 보이며, Locking Read는 현재 Version을 대상으로 하므로 Consistent Read와 섞을 때 관찰 결과가 복잡해질 수 있다.

InnoDB의 Consistent Read는 Snapshot 안에서 Phantom을 보지 않게 할 수 있고, Locking Read는 Index Range에 Next-key Lock을 사용해 삽입을 막는다. “SQL 표준 표 하나”만 보고 모든 DBMS의 Phantom 동작이 같다고 단정하면 안 된다.

SERIALIZABLE

동시 실행 결과가 Transaction을 하나씩 실행한 것과 동등하도록 강한 격리를 요구한다. 충돌 가능한 읽기와 쓰기가 대기하거나 실패할 수 있어 처리량과 응답 시간이 악화될 수 있다. 모든 업무에 적용하기보다 불변식과 충돌 빈도를 보고 선택한다.

Example

두 Session에서 READ COMMITTED와 REPEATABLE READ의 차이를 확인할 수 있다.

-- Session A
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1; -- 1000
 
-- Session B
START TRANSACTION;
UPDATE accounts SET balance = 500 WHERE id = 1;
COMMIT;
 
-- Session A: READ COMMITTED라면 500을 볼 수 있다.
SELECT balance FROM accounts WHERE id = 1;
COMMIT;

Session A를 REPEATABLE READ로 시작하면 두 번째 Consistent Read도 보통 첫 Snapshot의 1000을 본다. 실제 실습에서는 autocommit, Transaction 시작 시점, 첫 Consistent Read 시점, DBMS 종류를 함께 기록한다.

실무에서 발생하는 문제

  • 외부 API 호출을 Transaction 안에서 기다려 Lock과 Connection을 오래 점유한다.
  • Default Isolation Level을 확인하지 않고 다른 DBMS와 동일하게 동작한다고 가정한다.
  • 일반 SELECT 뒤 Application에서 값을 계산해 Update하여 Lost Update를 만든다.
  • Consistent Read와 Locking Read를 섞고 결과 차이를 버그로 오해한다.
  • 긴 REPEATABLE READ가 오래된 Version 정리를 지연시켜 Undo History와 Storage 압력을 키운다.

Trade-off

낮은 격리는 대기 시간을 줄일 수 있지만 Application이 이상 현상과 Retry를 더 많이 처리해야 한다. 높은 격리는 추론을 단순하게 만들 수 있지만 Lock Wait, Deadlock, Abort가 늘 수 있다. Isolation Level만 바꾸기 전에 Atomic Update, Unique Constraint, 낙관적 Lock, 비관적 Lock처럼 더 좁은 해결책을 비교한다.

흔한 오해

  • Transaction을 시작했다고 모든 읽기가 자동으로 Locking Read가 되지 않는다.
  • MVCC는 Lock을 완전히 없애지 않는다. Update와 Locking Read에는 Lock이 필요하다.
  • REPEATABLE READ가 모든 Business Race Condition을 해결하지 않는다.
  • Consistency는 DBMS가 모든 업무 규칙을 알아서 지켜준다는 뜻이 아니다.
  • Commit 순간에 Data Page가 반드시 즉시 물리 Disk에 기록된다는 단순 설명은 WAL과 Buffer 동작을 생략한다.

Production Considerations

Transaction Duration, Lock Wait, Deadlock, Rollback, Connection Pool Pending, Undo History를 같은 Timeline에서 본다. Spring에서는 @Transactional 범위, Propagation, readOnly, 예외 Rollback 규칙을 확인한다. Timeout과 Deadlock 재시도는 멱등성을 전제로 제한 횟수와 Backoff를 둔다.

Interview Questions

  1. READ COMMITTED와 REPEATABLE READ에서 같은 Row를 두 번 읽으면 어떤 차이가 생기는가?
  2. Dirty Read, Non-repeatable Read, Phantom Read를 각각 두 Transaction으로 설명하라.
  3. Undo Log와 Redo Log의 역할은 어떻게 다른가?
  4. Consistent Read와 SELECT FOR UPDATE의 목적은 어떻게 다른가?
  5. Long Transaction이 Lock을 잡지 않아도 운영에 부담을 줄 수 있는 이유는?

Follow-up으로 “MySQL InnoDB에서 REPEATABLE READ가 Phantom을 다루는 방식”, “Lost Update를 Isolation Level만으로 막을 수 있는가”, “Spring Transaction 범위에 외부 API 호출을 넣으면 무엇이 문제인가”를 설명할 수 있어야 한다.

Locking Read와 SELECT FOR UPDATE, Deadlock, Connection Pool을 이어서 본다.

SOURCE REFERENCES

이 문서의 근거

본문은 Dev Atlas 안에서 완결되며, 검증이 필요할 때만 원문을 확인할 수 있습니다.

원문 출처 보기 3