11장 : 동시성 제어
MVCC
MVCC(Multi-Version Concurrency Control)는 읽기와 쓰기가 서로를 차단하지 않는 동시성 제어 기법입니다. 현대 RDBMS의 핵심 기술로, 높은 동시성과 일관성을 동시에 달성합니다.
MVCC의 개념
전통적인 락 방식에서는 누군가 데이터를 수정 중이면 다른 사람은 읽기도 대기해야 합니다. MVCC는 데이터의 여러 버전을 유지하여 이 문제를 해결합니다.
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를 사용하지만 구현 방식이 다릅니다. 각 행에 숨겨진 컬럼을 추가하여 버전 체인을 관리합니다.
InnoDB 숨겨진 컬럼
ReadView
READ COMMITTED
* 매 SELECT마다 새로운 ReadView 생성
* → 다른 트랜잭션이 커밋하면 그 변경이 바로 보임
REPEATABLE READ
* 트랜잭션의 첫 번째 SELECT에서만 ReadView 생성
* → 이후 모든 SELECT가 같은 ReadView 사용
* → 트랜잭션 동안 일관된 스냅샷 유지Oracle vs MySQL MVCC 비교
| 비교 | Oracle | MySQL InnoDB |
|---|---|---|
| 버전 저장 | Undo Segment | Undo Log (공유 테이블스페이스) |
| 시점 식별 | SCN (전역 번호) | 트랜잭션 ID + ReadView |
| 스냅샷 기준 (RC) | 쿼리 시작 시점 | SELECT마다 새 ReadView |
| 스냅샷 기준 (RR) | N/A (RC가 기본) | 트랜잭션 첫 SELECT 시점 |
| 기본 격리 수준 | READ COMMITTED | REPEATABLE READ |
| SELECT 잠금 | 없음 (항상) | 없음 (MVCC 읽기) |
| Undo 고갈 | ORA-01555 | Undo 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 | 다중 버전으로 읽기와 쓰기의 비차단 구현 |
| Oracle | Undo Segment + SCN, Consistent Read |
| MySQL InnoDB | Undo Log + 트랜잭션 ID + ReadView |
| PostgreSQL | 데이터 파일에 이전 버전 보관 + VACUUM |
| ReadView | 가시성 판단 기준 (활성 트랜잭션 스냅샷) |
| ORA-01555 | Undo 소멸로 인한 스냅샷 복원 실패 |
| 비관적 잠금 | FOR UPDATE로 미리 잠금, 충돌 빈번 시 적합 |
| 낙관적 잠금 | version 비교, 충돌 드문 경우 적합 |
MVCC의 핵심은 쓰기가 읽기를 차단하지 않는다는 것입니다. 덕분에 읽기 위주의 워크로드에서 매우 높은 동시성을 달성할 수 있습니다. 단, 이전 버전을 보관하는 Undo 공간을 적절히 관리하고, 오래 걸리는 조회가 Undo 고갈을 일으키지 않도록 주의해야 합니다.
다음 장에서는 트랜잭션의 지속성을 보장하는 복구와 로깅을 다루겠습니다.