MVCC
MVCC(Multi-Version Concurrency Control)는 데이터를 여러 버전으로 보관해, 일반적인 일관 읽기가 쓰기 트랜잭션과 직접 충돌하지 않도록 만드는 동시성 제어 기법입니다. 읽기 위주의 워크로드에서는 대기 시간을 크게 줄이지만, 쓰기-쓰기 충돌·명시적 잠금·DDL 충돌은 여전히 별도로 제어됩니다.
MVCC의 개념
순수한 락 기반 읽기 모델에서는 누군가 데이터를 수정 중일 때 다른 트랜잭션의 잠금 읽기가 대기할 수 있습니다. MVCC는 오래된 커밋 버전을 재구성해 일반 SELECT가 같은 행의 최신 수정 작업을 기다리지 않게 만듭니다.
Oracle의 MVCC — Undo Segment와 SCN
Oracle은 Undo Segment와 SCN(System Change Number)으로 MVCC를 구현합니다.
SCN (System Change Number)
Undo Segment를 이용한 버전 관리
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를 사용하지만 구현 방식이 다릅니다. 클러스터드 인덱스 레코드에 숨겨진 시스템 필드를 두고, undo log를 따라가며 필요한 과거 버전을 재구성합니다.
InnoDB 숨겨진 컬럼
ReadView
READ COMMITTED
* 매 SELECT마다 새로운 ReadView 생성
* → 다른 트랜잭션이 커밋하면 그 변경이 바로 보임
REPEATABLE READ
* 트랜잭션의 첫 번째 consistent read(일반 SELECT)에서 ReadView 생성
* → 이후 모든 SELECT가 같은 ReadView 사용
* → 트랜잭션 동안 일관된 스냅샷 유지Oracle vs MySQL MVCC 비교
| 비교 | Oracle | MySQL InnoDB |
|---|---|---|
| 버전 저장 | Undo Segment | Undo Log / Undo Tablespace |
| 시점 식별 | SCN (전역 번호) | 트랜잭션 ID + ReadView |
| 스냅샷 기준 (RC) | 문장 시작 시점 | SELECT마다 새 ReadView |
| 트랜잭션 단위 스냅샷 | SERIALIZABLE / READ ONLY 사용 | REPEATABLE READ의 첫 consistent read |
| 기본 격리 수준 | READ COMMITTED | REPEATABLE READ |
| 일반 SELECT의 행 잠금 | DML 행 락 없음 | consistent read는 데이터 락 없음 |
| 오래된 버전 보관 실패 | ORA-01555 가능 | purge 지연·undo tablespace 증가 |
| 쓰기 vs 쓰기 | 행 잠금 대기 | 행 잠금 대기 |
낙관적 잠금 vs 비관적 잠금
MVCC 환경에서 실무적으로 자주 사용되는 두 가지 동시성 제어 패턴입니다.
비관적 잠금 (Pessimistic Locking)
-- 재고 차감 시: 먼저 락을 건 후 처리
-- Oracle
SELECT stock FROM products WHERE id = 101 FOR UPDATE;
-- → 해당 행에 X-Lock, 다른 트랜잭션의 UPDATE/DELETE/FOR UPDATE 차단
-- MySQL 8.0+ (즉시 실패)
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. 해시 또는 체크섬:
WHERE id = ? AND ORA_HASH(col1||col2||col3) = ?
충돌 가능성이 있으므로 핵심 정합성에는 version 컬럼이 더 명확
4. 전체 컬럼 비교 (일부 ORM 전략):
WHERE id = ? AND col1 = ? AND col2 = ? AND col3 = ?| 비교 | 비관적 | 낙관적 |
|---|---|---|
| 방식 | 미리 락 획득 (SELECT FOR UPDATE) | 커밋 시 충돌 검사 (version) |
| 충돌 시 | 대기 (블로킹) | 재시도 또는 에러 |
| 적합 상황 | 충돌 빈번 (좌석 예약, 재고) | 충돌 드문 (프로필 수정) |
| 성능 | 동시성↓ | 동시성↑ |
| 데드락 위험 | 있음 | 낮지만 다중 행 갱신에서는 가능 |
| DB 잠금 | 미리 길게 사용 | 조건부 UPDATE 시 짧게 사용 |
MVCC와 쓰기 충돌
MVCC는 일반 읽기가 과거 버전을 보게 해 주지만, 두 트랜잭션이 같은 현재 행을 쓰는 문제까지 자동으로 없애지는 않습니다. UPDATE/DELETE는 여전히 현재 버전에 대한 행 잠금, 대기, 충돌 감지의 영향을 받습니다. 애플리케이션에서 읽은 낡은 값을 절대값으로 저장하는 경로는 조건부 UPDATE, version 컬럼, SELECT ... FOR UPDATE 같은 별도 제어가 필요합니다.
MVCC와 격리 수준의 관계
MVCC에서의 Phantom Read 방지
MVCC의 스냅샷은 “같은 읽기 기준에서 다시 보게 하는 것”에 강하지만, Phantom Read 방지 방식은 DBMS와 격리 수준에 따라 다릅니다. Oracle은 문장 단위 읽기 일관성이 기본이고 트랜잭션 단위 일관성은 SERIALIZABLE 또는 READ ONLY 트랜잭션으로 다룹니다. MySQL InnoDB는 REPEATABLE READ에서 consistent read 스냅샷과 잠금 읽기의 next-key lock을 조합합니다. PostgreSQL은 SERIALIZABLE에서 SSI로 직렬화 이상을 탐지하고, SQL Server는 SNAPSHOT/RCSI 같은 row versioning 옵션을 사용합니다.
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 | 다중 버전으로 일반 읽기-쓰기 차단 감소 |
| Oracle | Undo Segment + SCN, Consistent Read |
| MySQL InnoDB | Undo Log + 트랜잭션 ID + ReadView |
| PostgreSQL | 데이터 파일에 이전 버전 보관 + VACUUM |
| ReadView | 가시성 판단 기준 (활성 트랜잭션 스냅샷) |
| ORA-01555 | Undo 소멸로 인한 스냅샷 복원 실패 |
| 비관적 잠금 | FOR UPDATE로 미리 잠금, 충돌 빈번 시 적합 |
| 낙관적 잠금 | version 비교, 충돌 드문 경우 적합 |
| SQL Server | RCSI/SNAPSHOT에서 row versioning 사용 |
MVCC의 핵심은 일반적인 일관 읽기가 쓰기와 직접 충돌하지 않도록 과거 버전을 제공한다는 것입니다. 덕분에 읽기 위주의 워크로드에서 높은 동시성을 달성할 수 있습니다. 단, 이전 버전을 보관하는 undo/버전 저장 공간을 적절히 관리하고, 오래 걸리는 조회가 purge 지연이나 오래된 버전 복원 실패를 만들지 않도록 주의해야 합니다.
다음 장에서는 트랜잭션의 지속성을 보장하는 복구와 로깅을 다루겠습니다.