icon

안동민 개발노트

11장 : 동시성 제어

MVCC

MVCC(Multi-Version Concurrency Control)는 읽기와 쓰기가 서로를 차단하지 않는 동시성 제어 기법입니다. 현대 RDBMS의 핵심 기술로, 높은 동시성과 일관성을 동시에 달성합니다.


MVCC의 개념

전통적인 락 방식에서는 누군가 데이터를 수정 중이면 다른 사람은 읽기도 대기해야 합니다. MVCC는 데이터의 여러 버전을 유지하여 이 문제를 해결합니다.


Oracle의 MVCC — Undo Segment와 SCN

Oracle은 Undo SegmentSCN(System Change Number)으로 MVCC를 구현합니다.

SCN (System Change Number)

Undo Segment를 이용한 버전 관리

Oracle Consistent Read 과정
T1이 SCN 1500에 SELECT A 실행:

1. 현재 블록 확인: A=300, SCN=2000
   → 2000 > 1500 → 이 버전은 너무 최신, 사용 불가

2. Undo 체인 따라감: A=200, SCN=1500
   → 1500 ≤ 1500 → 이 버전 사용 가능!
   → A = 200 반환

T2가 SCN 1200에 SELECT A 실행:

1. 현재 블록: SCN=2000 > 1200 → 불가
2. Undo 1: SCN=1500 > 1200 → 불가
3. Undo 2: SCN=1000 ≤ 1200 → 사용 가능!
   → A = 100 반환

ORA-01555: Snapshot Too Old


MySQL InnoDB의 MVCC

MySQL InnoDB도 MVCC를 사용하지만 구현 방식이 다릅니다. 각 행에 숨겨진 컬럼을 추가하여 버전 체인을 관리합니다.

InnoDB 숨겨진 컬럼

ReadView

ReadView 생성 시점과 격리 수준
READ COMMITTED
  * 매 SELECT마다 새로운 ReadView 생성
  * → 다른 트랜잭션이 커밋하면 그 변경이 바로 보임

REPEATABLE READ
  * 트랜잭션의 첫 번째 SELECT에서만 ReadView 생성
  * → 이후 모든 SELECT가 같은 ReadView 사용
  * → 트랜잭션 동안 일관된 스냅샷 유지

Oracle vs MySQL MVCC 비교

비교OracleMySQL InnoDB
버전 저장Undo SegmentUndo Log (공유 테이블스페이스)
시점 식별SCN (전역 번호)트랜잭션 ID + ReadView
스냅샷 기준 (RC)쿼리 시작 시점SELECT마다 새 ReadView
스냅샷 기준 (RR)N/A (RC가 기본)트랜잭션 첫 SELECT 시점
기본 격리 수준READ COMMITTEDREPEATABLE READ
SELECT 잠금없음 (항상)없음 (MVCC 읽기)
Undo 고갈ORA-01555Undo Log 비대화
쓰기 vs 쓰기행 잠금 대기행 잠금 대기

낙관적 잠금 vs 비관적 잠금

MVCC 환경에서 실무적으로 자주 사용되는 두 가지 동시성 제어 패턴입니다.

비관적 잠금 (Pessimistic Locking)

비관적 잠금
-- 재고 차감 시: 먼저 락을 건 후 처리
-- Oracle
SELECT stock FROM products WHERE id = 101 FOR UPDATE;
-- → 해당 행에 X-Lock, 다른 트랜잭션의 UPDATE/DELETE/FOR UPDATE 차단

-- MySQL (타임아웃 설정)
SELECT stock FROM products WHERE id = 101 FOR UPDATE NOWAIT;
-- → 잠금 획득 불가 시 즉시 에러 (대기 안 함)

SELECT stock FROM products WHERE id = 101 
FOR UPDATE SKIP LOCKED;
-- → 잠긴 행은 건너뛰고 잠금 가능한 행만 반환 (큐 패턴)

UPDATE products SET stock = stock - 1 WHERE id = 101;
COMMIT;

낙관적 잠금 (Optimistic Locking)

낙관적 잠금
-- version 컬럼을 이용하는 방식
SELECT id, stock, version FROM products WHERE id = 101;
-- → stock=10, version=3

-- 읽은 version으로 조건부 UPDATE
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 101 AND version = 3;
-- → 다른 트랜잭션이 먼저 수정했으면 version이 달라서 0건 갱신

-- 영향받은 행이 0이면 충돌 발생 → 재시도 또는 에러 처리
낙관적 잠금 — 다른 구현 방법
1. version 컬럼 (가장 일반적):
  WHERE id = ? AND version = ?
  SET version = version + 1

2. 타임스탬프:
  WHERE id = ? AND updated_at = ?
  SET updated_at = NOW()

3. 해시(Oracle):
  WHERE id = ? AND ORA_HASH(col1||col2||col3) = ?

4. 전체 컬럼 비교 (Hibernate default):
  WHERE id = ? AND col1 = ? AND col2 = ? AND col3 = ?
비교비관적낙관적
방식미리 락 획득 (SELECT FOR UPDATE)커밋 시 충돌 검사 (version)
충돌 시대기 (블로킹)재시도 또는 에러
적합 상황충돌 빈번 (좌석 예약, 재고)충돌 드문 (프로필 수정)
성능동시성↓동시성↑
데드락 위험있음없음
DB 잠금사용미사용

MVCC와 쓰기 충돌


MVCC와 격리 수준의 관계


MVCC에서의 Phantom Read 방지


MVCC 성능 고려사항


DBMS별 MVCC 구현 종합 비교


MVCC 실무 적용 패턴

긴 트랜잭션 탐지
Oracle
  SELECT s.sid, s.serial#, s.username,
         t.start_time, t.used_ublk
  FROM v$session s, v$transaction t
  WHERE s.taddr = t.addr
  ORDER BY t.start_time;

MySQL
  SELECT trx_id, trx_state, trx_started,
         TIMESTAMPDIFF(SECOND, trx_started, NOW()) duration
  FROM information_schema.innodb_trx
  ORDER BY trx_started;

PostgreSQL
  SELECT pid, usename, state, query_start,
         NOW() - query_start AS duration
  FROM pg_stat_activity
  WHERE state = 'idle in transaction'
  ORDER BY query_start;

정리

개념핵심 내용
MVCC다중 버전으로 읽기와 쓰기의 비차단 구현
OracleUndo Segment + SCN, Consistent Read
MySQL InnoDBUndo Log + 트랜잭션 ID + ReadView
PostgreSQL데이터 파일에 이전 버전 보관 + VACUUM
ReadView가시성 판단 기준 (활성 트랜잭션 스냅샷)
ORA-01555Undo 소멸로 인한 스냅샷 복원 실패
비관적 잠금FOR UPDATE로 미리 잠금, 충돌 빈번 시 적합
낙관적 잠금version 비교, 충돌 드문 경우 적합

MVCC의 핵심은 쓰기가 읽기를 차단하지 않는다는 것입니다. 덕분에 읽기 위주의 워크로드에서 매우 높은 동시성을 달성할 수 있습니다. 단, 이전 버전을 보관하는 Undo 공간을 적절히 관리하고, 오래 걸리는 조회가 Undo 고갈을 일으키지 않도록 주의해야 합니다.

다음 장에서는 트랜잭션의 지속성을 보장하는 복구와 로깅을 다루겠습니다.