트랜잭션 (Transaction)
트랜잭션은 데이터베이스의 상태를 변환시키는 하나의 논리적 작업 단위이다. 하나 이상의 SQL 문으로 구성되며, 전부 수행되거나 전부 수행되지 않아야 한다 (All or Nothing).
ACID 속성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| 1. 원자성 (Atomicity)
- 트랜잭션의 연산은 전부 반영되거나 전부 취소
- 중간 상태는 없음
- 보장 방법: 회복 시스템 (Undo/Redo)
2. 일관성 (Consistency)
- 트랜잭션 수행 전후로 DB의 일관성 유지
- 무결성 제약 조건을 항상 만족
- 예: 계좌 이체 → 두 계좌 합계는 불변
3. 격리성 (Isolation)
- 동시에 실행되는 트랜잭션은 서로 영향을 주지 않음
- 각 트랜잭션은 혼자 실행되는 것처럼 동작
- 보장 방법: 동시성 제어 (Locking, MVCC)
4. 지속성 (Durability)
- 성공적으로 완료된 트랜잭션의 결과는 영구 반영
- 시스템 장애 이후에도 결과가 보존
- 보장 방법: 로그 기반 회복
|
트랜잭션 상태
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| ┌──────────────────────────────┐
↓ │
[활동] → [부분 완료] → [완료] │
(Active) (Partially (Committed) │
Committed) │
│ │
↓ │
[실패] ──────────────→ [철회] │
(Failed) (Aborted) ───────┘
(Rollback 후 재시작 또는 종료)
활동(Active): 트랜잭션이 실행 중
부분 완료(Partially Committed): 마지막 연산 실행 후 커밋 대기
완료(Committed): COMMIT 후 결과 영구 반영
실패(Failed): 오류 발생으로 정상 수행 불가
철회(Aborted): ROLLBACK 후 트랜잭션 시작 전 상태로 복원
|
트랜잭션 SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| -- 트랜잭션 시작
BEGIN TRANSACTION; -- 또는 START TRANSACTION
-- 계좌 이체 예시
UPDATE Account SET balance = balance - 10000 WHERE id = 'A';
UPDATE Account SET balance = balance + 10000 WHERE id = 'B';
-- 성공 시
COMMIT;
-- 실패 시
ROLLBACK;
-- SAVEPOINT
BEGIN TRANSACTION;
UPDATE Account SET balance = balance - 5000 WHERE id = 'A';
SAVEPOINT sp1;
UPDATE Account SET balance = balance + 5000 WHERE id = 'B';
-- B 계좌 업데이트에 문제 발생
ROLLBACK TO sp1; -- sp1까지만 롤백
-- 다른 처리...
COMMIT;
|
동시성 문제
여러 트랜잭션이 동시에 실행될 때 발생할 수 있는 문제.
문제 유형
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| 1. 갱신 분실 (Lost Update)
T1: Read(X) → X = X + 1 → Write(X)
T2: Read(X) → X = X + 2 → Write(X)
T1이 X를 읽고 수정하기 전에 T2도 X를 읽으면
T1의 갱신이 T2에 의해 덮어씌워져 소실
2. 비완료 읽기 / 오손 읽기 (Dirty Read)
T1: Write(X) → (아직 COMMIT 안 함)
T2: Read(X) → T1이 ROLLBACK
T2가 읽은 값은 존재하지 않는 값
커밋되지 않은 데이터를 읽음
3. 비반복 읽기 (Non-Repeatable Read)
T1: Read(X) → ... → Read(X) (값이 다름)
T2: Write(X), COMMIT
같은 트랜잭션 내에서 같은 데이터를 두 번 읽었는데 값이 다름
4. 팬텀 읽기 (Phantom Read)
T1: SELECT COUNT(*) WHERE dept='CS' → 10
T2: INSERT INTO Student ... WHERE dept='CS', COMMIT
T1: SELECT COUNT(*) WHERE dept='CS' → 11
같은 조건으로 조회했는데 행의 수가 달라짐 (새 행 삽입/삭제)
|
격리 수준 (Isolation Level)
4단계 격리 수준
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| Dirty Read Non-Repeatable Phantom Read
Read
Level 0: READ UNCOMMITTED O O O
Level 1: READ COMMITTED X O O
Level 2: REPEATABLE READ X X O
Level 3: SERIALIZABLE X X X
O = 발생 가능, X = 방지
높은 격리 수준:
✅ 데이터 일관성 높음
❌ 동시성(성능) 낮음
낮은 격리 수준:
✅ 동시성(성능) 높음
❌ 데이터 이상 현상 발생 가능
|
1
2
3
4
| -- 격리 수준 설정
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
|
각 격리 수준 설명
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| READ UNCOMMITTED (Level 0)
- 커밋되지 않은 데이터도 읽을 수 있음
- 가장 빠르지만 가장 위험
- 거의 사용하지 않음
READ COMMITTED (Level 1)
- 커밋된 데이터만 읽음
- Oracle, PostgreSQL 기본값
- Non-Repeatable Read 가능
REPEATABLE READ (Level 2)
- 트랜잭션 시작 시점의 스냅샷 읽음
- MySQL InnoDB 기본값
- Phantom Read 가능 (MySQL은 Gap Lock으로 방지)
SERIALIZABLE (Level 3)
- 완전한 직렬 실행과 동일한 결과 보장
- 성능이 가장 낮음
- 데이터 정합성이 절대적으로 중요한 경우
|
Locking (잠금)
Lock의 종류
1
2
3
4
5
6
7
8
9
| 1. 공유 락 (Shared Lock, S-Lock)
- 읽기 연산에 사용
- 여러 트랜잭션이 동시에 S-Lock 획득 가능
- S-Lock이 걸린 데이터에 X-Lock 불가
2. 배타 락 (Exclusive Lock, X-Lock)
- 쓰기 연산에 사용
- 하나의 트랜잭션만 획득 가능
- X-Lock이 걸린 데이터에 S-Lock, X-Lock 모두 불가
|
Lock 호환성 행렬
1
2
3
4
5
6
7
8
9
| 기존 Lock
S-Lock X-Lock
요청 S ✅ 허용 ❌ 대기
X ❌ 대기 ❌ 대기
S-S: 호환 (동시 읽기 가능)
S-X: 비호환 (읽기 중 쓰기 불가)
X-S: 비호환 (쓰기 중 읽기 불가)
X-X: 비호환 (동시 쓰기 불가)
|
2단계 잠금 규약 (2PL: Two-Phase Locking)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| 트랜잭션의 잠금을 2단계로 나눔:
1. 확장 단계 (Growing Phase)
- Lock만 획득, 해제 안 함
2. 축소 단계 (Shrinking Phase)
- Lock만 해제, 획득 안 함
- Lock을 하나라도 해제하면 더 이상 획득 불가
Lock 수
│ ╱╲
│ ╱ ╲
│ ╱ ╲
│ ╱ ╲
│ ╱ ╲
└────────────── 시간
확장 축소
(Growing) (Shrinking)
↑
Lock Point (최대 Lock 수 시점)
2PL 보장: 직렬 가능성 (Serializability)
2PL 한계: 교착 상태 (Deadlock) 가능
|
2PL 변형
1
2
3
4
5
6
7
8
9
10
11
12
| 1. 기본 2PL (Basic 2PL)
- 위 설명 그대로
- 연쇄 복귀 (Cascading Rollback) 가능
2. 엄격한 2PL (Strict 2PL)
- X-Lock은 트랜잭션 종료(COMMIT/ROLLBACK)까지 유지
- 연쇄 복귀 방지
- 가장 널리 사용
3. 강한 2PL (Rigorous 2PL)
- 모든 Lock을 트랜잭션 종료까지 유지
- 구현이 간단하지만 동시성 감소
|
교착 상태 (Deadlock)
발생 조건
1
2
3
4
5
6
7
8
9
10
11
12
| 두 트랜잭션이 서로가 보유한 Lock을 기다리는 상태
T1: Lock(A) → Lock(B) 요청 (대기)
T2: Lock(B) → Lock(A) 요청 (대기)
→ 무한 대기
교착 상태 필요 조건 (4가지 모두 충족):
1. 상호 배제 (Mutual Exclusion): Lock은 공유 불가 (X-Lock)
2. 점유와 대기 (Hold and Wait): Lock 보유한 채 다른 Lock 대기
3. 비선점 (No Preemption): 다른 트랜잭션의 Lock 강제 해제 불가
4. 순환 대기 (Circular Wait): 대기 관계가 순환
|
해결 방법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| 1. 예방 (Prevention)
├── Wait-Die: 오래된 T가 대기, 젊은 T가 대기하면 Rollback
├── Wound-Wait: 오래된 T가 강제 선점, 젊은 T가 대기
└── 타임스탬프 기반: 트랜잭션 시작 시간으로 우선순위
2. 회피 (Avoidance)
- 자원 할당 그래프로 순환 감지 전에 회피
3. 탐지와 회복 (Detection & Recovery)
- 대기 그래프(Wait-for Graph) 유지
- 주기적으로 순환 탐지
- 순환 발견 시 하나의 트랜잭션을 Rollback (희생자 선택)
- 가장 일반적인 방법
4. 타임아웃 (Timeout)
- 일정 시간 Lock 획득 못하면 Rollback
- 구현 간단하지만 정확하지 않음
|
MVCC (Multi-Version Concurrency Control)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| Lock 기반의 대안으로, 데이터의 여러 버전을 유지하여 동시성을 높이는 기법.
원리:
- 데이터 수정 시 이전 버전을 보존
- 읽기 연산은 Lock 없이 적절한 버전을 읽음
- 쓰기 연산만 Lock 사용
장점:
- 읽기와 쓰기가 서로 블로킹하지 않음
- 읽기 성능 크게 향상
- 대부분의 실무 DBMS가 채택
동작 방식 (PostgreSQL 예):
T1(시작: t=10): SELECT → 시점 t=10의 스냅샷 읽기
T2(시작: t=15): UPDATE → 새 버전 생성 (t=15)
T1: SELECT → 여전히 t=10의 스냅샷 (T2 변경 안 보임)
사용하는 DBMS:
- PostgreSQL: MVCC (Tuple Versioning)
- MySQL InnoDB: MVCC (Undo Log 기반)
- Oracle: MVCC (Undo Tablespace 기반)
|
직렬 가능성 (Serializability)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| 여러 트랜잭션의 동시 실행 결과가
어떤 직렬 실행(Serial Schedule)의 결과와 동일한 경우
"직렬 가능(Serializable)"하다고 함
직렬 스케줄: T1 → T2 → T3 (순차 실행)
→ 항상 정확하지만 성능 나쁨
직렬 가능 스케줄: 동시 실행하되 직렬 실행과 동일한 결과
→ 동시성 + 정확성
충돌 직렬 가능성 (Conflict Serializability):
- 충돌하는 연산의 순서가 어떤 직렬 스케줄과 동일
- 충돌: 같은 데이터에 대해, 하나 이상이 Write인 연산 쌍
- Read-Write 충돌
- Write-Read 충돌
- Write-Write 충돌
- 선행 그래프(Precedence Graph)에 순환이 없으면 충돌 직렬 가능
|
핵심 정리
1
2
3
4
5
6
7
| 1. ACID: 원자성, 일관성, 격리성, 지속성
2. 동시성 문제: Dirty Read, Non-Repeatable Read, Phantom Read
3. 격리 수준: READ UNCOMMITTED → READ COMMITTED → REPEATABLE READ → SERIALIZABLE
4. Lock: S-Lock(읽기), X-Lock(쓰기)
5. 2PL: 확장 → 축소 단계 → 직렬 가능성 보장
6. Deadlock: 예방(Wait-Die) / 탐지(Wait-for Graph) / 타임아웃
7. MVCC: 다중 버전으로 읽기-쓰기 비블로킹
|
면접 포인트
자주 나오는 질문
Q1. ACID 속성을 설명하라.
- Atomicity: 전부 수행 또는 전부 취소 (All or Nothing)
- Consistency: 트랜잭션 전후 무결성 제약 유지
- Isolation: 동시 실행 트랜잭션 간 간섭 없음
- Durability: 커밋된 결과는 영구 보존
Q2. 격리 수준의 종류와 차이는?
- READ UNCOMMITTED: 모든 이상 허용 (Dirty Read 포함)
- READ COMMITTED: Dirty Read 방지 (Oracle 기본)
- REPEATABLE READ: Non-Repeatable Read 방지 (MySQL 기본)
- SERIALIZABLE: 모든 이상 방지 (성능 최저)
- 격리 수준 높을수록 안전하지만 동시성 감소
Q3. Deadlock이란? 어떻게 해결하는가?
- 두 트랜잭션이 서로의 Lock을 기다리는 무한 대기 상태
- 예방: Wait-Die, Wound-Wait (타임스탬프 기반)
- 탐지: Wait-for Graph에서 순환 탐지 → 희생자 Rollback
- 타임아웃: 일정 시간 후 자동 Rollback
Q4. 2PL이란?
- 확장 단계: Lock만 획득
- 축소 단계: Lock만 해제
- 직렬 가능성을 보장하지만 Deadlock은 발생 가능
- Strict 2PL: X-Lock을 COMMIT까지 유지 → 연쇄 복귀 방지
Q5. MVCC란?
- 데이터의 여러 버전을 유지하여 읽기-쓰기 충돌을 방지
- 읽기 시 Lock이 필요 없어 동시성이 높음
- PostgreSQL, MySQL InnoDB, Oracle 등에서 사용
- 트랜잭션 시작 시점의 스냅샷을 읽음
Q6. Dirty Read, Non-Repeatable Read, Phantom Read의 차이는?
- Dirty Read: 커밋되지 않은 데이터를 읽음
- Non-Repeatable Read: 같은 행을 두 번 읽었는데 값이 다름
- Phantom Read: 같은 조건으로 조회했는데 행의 수가 다름 (삽입/삭제)