회복과 병행 제어
회복과 병행 제어
트랜잭션 관리를 통한 데이터베이스의 일관성 유지, 장애 회복, 병행 제어를 다룬다.
1. 트랜잭션
1.1 트랜잭션의 개념
트랜잭션(Transaction) 은 작업 하나를 수행하는 데 필요한 데이터베이스 연산들의 모임으로, 논리적인 작업의 단위다.
┌─────────────────────────────────────────────────────────┐
│ 트랜잭션 예시 │
│ (계좌 이체) │
├─────────────────────────────────────────────────────────┤
│ BEGIN TRANSACTION │
│ 1. A계좌에서 10,000원 출금 (UPDATE) │
│ 2. B계좌에 10,000원 입금 (UPDATE) │
│ COMMIT (또는 ROLLBACK) │
└─────────────────────────────────────────────────────────┘
↓
모두 성공하거나, 모두 취소되어야 함 (All or Nothing)1.2 트랜잭션의 특성 (ACID)
| 특성 | 영문 | 설명 | DBMS 기능 |
|---|---|---|---|
| 원자성 | Atomicity | 모두 수행되거나, 하나도 수행되지 않아야 함 | 회복 기능 |
| 일관성 | Consistency | 수행 전후 데이터베이스가 일관된 상태 유지 | 병행 제어 |
| 격리성 | Isolation | 수행 중인 트랜잭션의 중간 결과에 다른 트랜잭션 접근 불가 | 병행 제어 |
| 지속성 | Durability | 완료된 트랜잭션 결과는 영구적으로 보존 | 회복 기능 |
┌─────────────────────────────────────────────────────────────┐
│ ACID 특성 시각화 │
├─────────────────────────────────────────────────────────────┤
│ │
│ [원자성] [일관성] [격리성] [지속성] │
│ │ │ │ │ │
│ All or 일관된 독립적 영구적 │
│ Nothing 상태유지 수행 보존 │
│ │ │ │ │ │
│ └────────────────┴─────────────────┴────────────────┘ │
│ │ │
│ ┌─────────┴─────────┐ │
│ │ 회복 기능 │ 병행 제어 기능 │
│ │ (원자성, 지속성) │ (일관성, 격리성) │
│ └───────────────────┘ │
└─────────────────────────────────────────────────────────────┘1.3 트랜잭션의 연산
| 연산 | 설명 |
|---|---|
| COMMIT | 트랜잭션이 성공적으로 완료됨을 선언 (작업 완료) |
| ROLLBACK | 트랜잭션 수행이 실패했음을 선언 (작업 취소) |
-- 트랜잭션 예제: 계좌 이체
BEGIN TRANSACTION;
UPDATE 계좌 SET 잔액 = 잔액 - 10000 WHERE 계좌번호 = 'A001';
UPDATE 계좌 SET 잔액 = 잔액 + 10000 WHERE 계좌번호 = 'B001';
-- 모든 연산이 성공하면
COMMIT;
-- 오류 발생 시
-- ROLLBACK;1.4 트랜잭션의 상태
┌─────────────────────────────────────────────────────────────────┐
│ 트랜잭션 상태 전이도 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ │
│ │ 활동 │ ←── 트랜잭션 시작 │
│ │ (Active) │ │
│ └────┬─────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ │ │ │ │
│ ▼ │ ▼ │
│ ┌──────────┐ │ ┌──────────┐ │
│ │ 부분완료 │ │ │ 실패 │ │
│ │(Partially│ │ │ (Failed) │ │
│ │Committed)│ │ └────┬─────┘ │
│ └────┬─────┘ │ │ │
│ │ │ │ rollback │
│ │ commit │ ▼ │
│ ▼ │ ┌──────────┐ │
│ ┌──────────┐ │ │ 철회 │ │
│ │ 완료 │ │ │(Aborted) │ │
│ │(Committed)│ │ └──────────┘ │
│ └──────────┘ │ │ │
│ │ │ │ │
│ └─────────────────┴─────────────┘ │
│ 트랜잭션 종료 │
└─────────────────────────────────────────────────────────────────┘| 상태 | 설명 |
|---|---|
| 활동(Active) | 트랜잭션이 수행을 시작하여 현재 수행 중인 상태 |
| 부분 완료(Partially Committed) | 마지막 연산이 실행된 직후 상태 (아직 DB 반영 전) |
| 완료(Committed) | commit 연산 실행, 결과가 DB에 반영된 상태 |
| 실패(Failed) | 장애로 인해 수행이 중단된 상태 |
| 철회(Aborted) | rollback 연산 실행, 트랜잭션 이전 상태로 복귀 |
2. 장애와 회복
2.1 장애의 유형
| 유형 | 설명 | 원인 |
|---|---|---|
| 트랜잭션 장애 | 트랜잭션 수행 중 오류 발생 | 논리적 오류, 잘못된 데이터 입력, 자원 부족 |
| 시스템 장애 | 하드웨어 결함으로 수행 중단 | 메인 메모리 정보 손실, 교착 상태 |
| 미디어 장애 | 디스크 장치 결함 | 디스크 헤드 손상, 고장 |
2.2 저장 장치의 종류
┌─────────────────────────────────────────────────────────────┐
│ 저장 장치 계층 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ 휘발성 저장장치 │ 장애 시 데이터 손실 │
│ │ (메인 메모리) │ 속도: 빠름 │
│ └────────┬────────┘ │
│ │ input/output │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 비휘발성 저장장치 │ 장애 시에도 데이터 유지 │
│ │ (디스크, SSD) │ 단, 저장장치 자체 고장 시 손실 가능 │
│ └────────┬────────┘ │
│ │ dump │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 안정 저장장치 │ 복사본 다중 보관 │
│ │ (RAID, 백업) │ 어떤 장애에도 데이터 보존 │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘2.3 데이터 이동 연산
┌─────────────────────────────────────────────────────────────────┐
│ 데이터 이동 연산 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ read(X) ┌──────────────┐ │
│ │ 프로그램 │ ◄──────────────── │ 메인메모리 │ │
│ │ 변수 │ │ 버퍼 블록 │ │
│ │ │ ────────────────► │ │ │
│ └──────────────┘ write(X) └──────┬───────┘ │
│ │ │
│ input(X) │ ▲ output(X) │
│ ▼ │ │
│ ┌──────────────┐ │
│ │ 디스크 │ │
│ │ 디스크 블록 │ │
│ └──────────────┘ │
│ │
│ • input(X): 디스크 → 메인메모리 │
│ • output(X): 메인메모리 → 디스크 │
│ • read(X): 버퍼 블록 → 프로그램 변수 │
│ • write(X): 프로그램 변수 → 버퍼 블록 │
│ │
└─────────────────────────────────────────────────────────────────┘2.4 회복 기법
복사본 생성 방법
| 방법 | 설명 |
|---|---|
| 덤프(Dump) | 데이터베이스 전체를 주기적으로 다른 저장 장치에 복사 |
| 로그(Log) | 변경 연산마다 이전 값과 이후 값을 별도 파일에 기록 |
회복 연산
| 연산 | 설명 | 사용 시점 |
|---|---|---|
| redo(재실행) | 로그를 이용해 변경 연산을 재실행 | DB 전반적으로 손상된 경우 |
| undo(취소) | 로그를 이용해 변경 연산을 취소 | 변경 중인 내용만 손상된 경우 |
로그 레코드의 종류
┌─────────────────────────────────────────────────────────────────┐
│ 로그 파일 예시 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ <T1, start> ← T1 시작 │
│ <T1, A, 1000, 950> ← T1이 A를 1000에서 950으로 │
│ <T1, B, 2000, 2050> ← T1이 B를 2000에서 2050으로 │
│ <T1, commit> ← T1 완료 │
│ <T2, start> ← T2 시작 │
│ <T2, C, 500, 400> ← T2가 C를 500에서 400으로 │
│ <checkpoint [T2]> ← 검사 시점 (T2 수행 중) │
│ <T2, D, 300, 350> ← T2가 D를 300에서 350으로 │
│ <T2, commit> ← T2 완료 │
│ <T3, start> ← T3 시작 │
│ <T3, E, 100, 80> ← T3가 E를 100에서 80으로 │
│ ────── 장애 발생 ────── │
│ │
│ → T3는 commit 없음 → undo 필요 │
│ → T2는 checkpoint 이후 commit → redo 필요 │
│ │
└─────────────────────────────────────────────────────────────────┘로그 회복 기법 비교
| 구분 | 즉시 갱신 회복 | 지연 갱신 회복 |
|---|---|---|
| DB 반영 시점 | 트랜잭션 수행 중 즉시 반영 | 부분 완료 후 한 번에 반영 |
| 로그 기록 | <Ti, X, old, new> | <Ti, X, new> |
| 장애 시 (commit 전) | undo 연산 | 로그 무시 (버림) |
| 장애 시 (commit 후) | redo 연산 | redo 연산 |
┌────────────────────────────────────────────────────────────────────────┐
│ 즉시 갱신 vs 지연 갱신 비교 │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ [즉시 갱신] [지연 갱신] │
│ │
│ 트랜잭션 시작 트랜잭션 시작 │
│ │ │ │
│ ▼ ▼ │
│ ┌───────┐ ┌───────┐ │
│ │로그기록│ │로그기록│ │
│ └───┬───┘ └───┬───┘ │
│ │ │ │
│ ▼ │ (DB 반영 없음) │
│ ┌───────┐ │ │
│ │DB 반영│ ← 즉시 반영 │ │
│ └───┬───┘ │ │
│ │ │ │
│ ▼ ▼ │
│ ┌───────┐ ┌───────┐ │
│ │commit │ │commit │ │
│ └───┬───┘ └───┬───┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌───────┐ │
│ │ │DB 반영│ ← 이 시점에 반영 │
│ │ └───────┘ │
│ ▼ ▼ │
│ 완료 완료 │
│ │
│ 장애 시: undo 또는 redo 필요 장애 시: redo만 필요 │
│ │
└────────────────────────────────────────────────────────────────────────┘검사 시점(Checkpoint) 회복 기법
로그 전체를 분석하는 비효율성을 해결하기 위해 일정 간격으로 검사 시점을 설정한다.
┌─────────────────────────────────────────────────────────────────────────┐
│ 검사 시점 회복 기법 예시 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 시간 → │
│ ────────────────────────────────────────────────────────────────► │
│ │
│ T1 ├──────────┤ ← checkpoint 전 완료 │
│ (회복 불필요) │
│ │
│ T2 ├────────────────────┤ ← checkpoint 전 시작, │
│ checkpoint 후 완료 │
│ checkpoint (redo 필요) │
│ │ │
│ T3 ├───────────┼──────────┤ ← checkpoint 전 시작, │
│ │ 장애 시점에 수행 중 │
│ │ (undo 필요) │
│ │ │
│ T4 │ ├──────────┤ ← checkpoint 후 완료 │
│ │ (redo 필요) │
│ │ │
│ T5 │ ├───────── 장애 발생 │
│ │ (undo 필요) │
│ │ │ │
│ ▼ ▼ │
│ <checkpoint> 장애 발생 │
│ │
└─────────────────────────────────────────────────────────────────────────┘3. 병행 제어
3.1 병행 수행의 개념
병행 수행(Concurrency): 여러 트랜잭션이 동시에 수행되는 것 (인터리빙 방식)
┌─────────────────────────────────────────────────────────────────┐
│ 인터리빙 방식 수행 예시 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [직렬 수행] │
│ T1: ████████████████ │
│ T2: ████████████████ │
│ 시간 → │
│ │
│ [병행 수행 - 인터리빙] │
│ T1: ████ ████ ████ ████ │
│ T2: ████ ████ ████ │
│ 시간 → │
│ │
└─────────────────────────────────────────────────────────────────┘3.2 병행 수행의 문제점
갱신 분실 (Lost Update)
┌─────────────────────────────────────────────────────────────────┐
│ 갱신 분실 예시 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 초기값: X = 100 │
│ │
│ T1 T2 │
│ ───── ───── │
│ read(X) → X=100 │
│ read(X) → X=100 │
│ X = X - 10 │
│ X = 90 │
│ X = X + 20 │
│ X = 120 │
│ write(X) → X=90 │
│ write(X) → X=120 │
│ │
│ 결과: X = 120 │
│ 문제: T1의 갱신(-10)이 분실됨 │
│ 기대값: 100 - 10 + 20 = 110 │
│ │
└─────────────────────────────────────────────────────────────────┘모순성 (Inconsistency)
┌─────────────────────────────────────────────────────────────────┐
│ 모순성 예시 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 초기값: X = 100, Y = 100 (합계: 200) │
│ │
│ T1 (X, Y 각각 2배) T2 (X+Y 합계 계산) │
│ ───── ───── │
│ read(X) → 100 │
│ X = X * 2 = 200 │
│ write(X) │
│ read(X) → 200 │
│ read(Y) → 100 │
│ read(Y) → 100 합계 = 300 ← 모순! │
│ Y = Y * 2 = 200 │
│ write(Y) │
│ │
│ T1 완료 후 올바른 합계: 400 │
│ T2가 읽은 합계: 300 (일관성 없는 상태에서 읽음) │
│ │
└─────────────────────────────────────────────────────────────────┘연쇄 복귀 (Cascading Rollback)
┌─────────────────────────────────────────────────────────────────┐
│ 연쇄 복귀 예시 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ T1 T2 │
│ ───── ───── │
│ read(X) │
│ X = X - 10 │
│ write(X) │
│ read(X) ← T1이 변경한 X 읽음 │
│ X = X + 5 │
│ write(X) │
│ 장애 발생! │
│ ROLLBACK │
│ ROLLBACK 필요! ← 연쇄 복귀 │
│ │
│ 문제: T2가 이미 commit 했다면 rollback 불가능! │
│ │
└─────────────────────────────────────────────────────────────────┘3.3 트랜잭션 스케줄
| 스케줄 유형 | 설명 | 결과 정확성 |
|---|---|---|
| 직렬 스케줄 | 트랜잭션별로 순차 실행 (인터리빙 X) | 항상 정확 |
| 비직렬 스케줄 | 인터리빙 방식으로 병행 수행 | 보장 안됨 |
| 직렬 가능 스케줄 | 직렬 스케줄과 동일한 결과를 내는 비직렬 스케줄 | 정확 |
┌─────────────────────────────────────────────────────────────────────────┐
│ 스케줄 유형 비교 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ [직렬 스케줄] │
│ T1: read(A) → write(A) → read(B) → write(B) │
│ T2: read(A) → write(A) │
│ ✓ 정확한 결과 보장 │
│ ✗ 병행성 없음 │
│ │
│ [비직렬 스케줄 - 직렬 가능] │
│ T1: read(A) → write(A) ─────────────────────→ read(B) → write(B) │
│ T2: read(A) → write(A) │
│ ✓ 직렬 스케줄과 동일한 결과 │
│ ✓ 병행성 확보 │
│ │
│ [비직렬 스케줄 - 직렬 불가능] │
│ T1: read(A) ─────────────────→ write(A) │
│ T2: read(A) → write(A) │
│ ✗ 갱신 분실 등 문제 발생 가능 │
│ │
└─────────────────────────────────────────────────────────────────────────┘3.4 로킹(Locking) 기법
Lock 연산의 종류
| 연산 | 설명 | 다른 트랜잭션 허용 |
|---|---|---|
| 공용 Lock (S-Lock) | read만 가능, write 불가 | 공용 Lock만 허용 |
| 전용 Lock (X-Lock) | read, write 모두 가능 | 모든 Lock 불허 |
┌─────────────────────────────────────────────────────────────────┐
│ Lock 양립성 매트릭스 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ │ 공용 Lock (S) │ 전용 Lock (X) │ │
│ ───────────┼─────────────────┼─────────────────┤ │
│ 공용 Lock │ ✓ │ ✗ │ │
│ ───────────┼─────────────────┼─────────────────┤ │
│ 전용 Lock │ ✗ │ ✗ │ │
│ │
│ ✓: 양립 가능 (동시에 Lock 획득 가능) │
│ ✗: 양립 불가 (대기해야 함) │
│ │
└─────────────────────────────────────────────────────────────────┘로킹 단위와 병행성
┌─────────────────────────────────────────────────────────────────┐
│ 로킹 단위 트레이드오프 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 로킹 단위 ──────────────────────────────────────────► │
│ 작음 큼 │
│ (속성) (튜플) (릴레이션) (데이터베이스) │
│ │
│ 병행성 높음 ◄──────────────────────────────► 낮음 │
│ 제어복잡도 높음 ◄──────────────────────────────► 낮음 │
│ 오버헤드 높음 ◄──────────────────────────────► 낮음 │
│ │
└─────────────────────────────────────────────────────────────────┘3.5 2단계 로킹 규약 (2PL)
2단계 로킹 규약을 준수하면 직렬 가능성이 보장된다.
| 단계 | 설명 |
|---|---|
| 확장 단계 | Lock 연산만 가능, Unlock 불가 |
| 축소 단계 | Unlock 연산만 가능, Lock 불가 |
┌─────────────────────────────────────────────────────────────────────────┐
│ 2단계 로킹 규약 시각화 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Lock 수 │
│ ▲ │
│ │ Lock Point │
│ │ ▼ │
│ │ ┌────┐ │
│ │ / \ │
│ │ / \ │
│ │ / \ │
│ │ / \ │
│ │ / \ │
│ │ / \ │
│ │ / \ │
│ └──────────────────────────────────────────────► 시간 │
│ │ 확장 단계 │ 축소 단계 │ │
│ │ (Lock만) │ (Unlock만) │ │
│ │
│ 규칙: 첫 번째 Unlock 전에 필요한 모든 Lock을 획득해야 함 │
│ │
└─────────────────────────────────────────────────────────────────────────┘2PL 예제
┌─────────────────────────────────────────────────────────────────────────┐
│ 2PL 준수 vs 위반 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ [2PL 준수] [2PL 위반] │
│ ───────── ───────── │
│ lock(A) ┐ lock(A) ┐ │
│ read(A) │ 확장 read(A) │ │
│ lock(B) │ 단계 unlock(A) ┘ ← Unlock 발생 │
│ read(B) ┘ lock(B) ← Lock 시도 (위반!) │
│ write(A) read(B) │
│ unlock(A) ┐ 축소 unlock(B) │
│ write(B) │ 단계 │
│ unlock(B) ┘ ✗ 직렬 가능성 보장 안됨 │
│ │
│ ✓ 직렬 가능성 보장 │
│ │
└─────────────────────────────────────────────────────────────────────────┘3.6 교착 상태 (Deadlock)
2PL을 사용해도 교착 상태(Deadlock) 가 발생할 수 있다.
┌─────────────────────────────────────────────────────────────────┐
│ 교착 상태 예시 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ T1 T2 │
│ ───── ───── │
│ lock(A) │
│ lock(B) │
│ lock(B) ← 대기 │
│ │ lock(A) ← 대기 │
│ │ │ │
│ └───────────────────────────────┘ │
│ 서로 대기 → 교착 상태! │
│ │
│ ┌─────┐ │
│ │ A │ ◄─── T1이 Lock │
│ └──┬──┘ │
│ │ T2 대기 │
│ ▼ │
│ T1 대기 ──► ┌─────┐ │
│ │ B │ ◄─── T2가 Lock │
│ └─────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘교착 상태 해결 방법
| 방법 | 설명 |
|---|---|
| 예방 | 교착 상태가 발생하지 않도록 사전에 조치 |
| 탐지 | 교착 상태 발생 시 탐지하여 해결 |
| 타임아웃 | 일정 시간 대기 후 자동 롤백 |
4. 실무 SQL 예제
4.1 트랜잭션 제어
-- MySQL 트랜잭션 예제
START TRANSACTION;
-- 계좌 이체
UPDATE accounts SET balance = balance - 10000 WHERE account_id = 'A001';
UPDATE accounts SET balance = balance + 10000 WHERE account_id = 'B001';
-- 잔액 확인
SELECT balance FROM accounts WHERE account_id IN ('A001', 'B001');
-- 문제없으면 커밋
COMMIT;
-- 문제 발생 시 롤백
-- ROLLBACK;4.2 격리 수준 설정
-- 격리 수준 확인
SELECT @@transaction_isolation;
-- 격리 수준 설정
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;4.3 Lock 확인 및 관리
-- MySQL: 현재 Lock 상태 확인
SELECT * FROM performance_schema.data_locks;
-- 대기 중인 Lock 확인
SELECT * FROM performance_schema.data_lock_waits;
-- 명시적 Lock (FOR UPDATE: 전용 Lock)
SELECT * FROM accounts WHERE account_id = 'A001' FOR UPDATE;
-- 공용 Lock
SELECT * FROM accounts WHERE account_id = 'A001' FOR SHARE;5. 핵심 요약
┌─────────────────────────────────────────────────────────────────────────┐
│ 회복과 병행 제어 요약 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 트랜잭션 │
│ ├── ACID 특성: 원자성, 일관성, 격리성, 지속성 │
│ ├── 연산: COMMIT, ROLLBACK │
│ └── 상태: 활동 → 부분완료 → 완료 (또는 실패 → 철회) │
│ │
│ 회복 │
│ ├── 장애 유형: 트랜잭션, 시스템, 미디어 │
│ ├── 회복 연산: redo(재실행), undo(취소) │
│ └── 회복 기법: 즉시갱신, 지연갱신, 검사시점, 미디어 │
│ │
│ 병행 제어 │
│ ├── 문제점: 갱신 분실, 모순성, 연쇄 복귀 │
│ ├── 스케줄: 직렬, 비직렬, 직렬 가능 │
│ ├── 로킹: 공용 Lock(S), 전용 Lock(X) │
│ └── 2PL: 확장 단계(Lock만) → 축소 단계(Unlock만) │
│ │
└─────────────────────────────────────────────────────────────────────────┘| 구분 | DBMS 기능 | 보장하는 특성 |
|---|---|---|
| 회복 기능 | 로그, 덤프, redo/undo | 원자성, 지속성 |
| 병행 제어 | 로킹, 2PL | 일관성, 격리성 |