계좌 이체를 생각해 보자. A 계좌에서 1만 원을 빼고, B 계좌에 1만 원을 더한다. 두 개의 UPDATE 문이다. 그런데 첫 번째 UPDATE가 끝난 직후 서버가 죽으면? 1만 원은 세상에서 사라진다.
이 문제를 막기 위해 DBMS는 “여러 연산을 하나의 논리적 작업 단위로 묶는” 장치를 제공한다. 그게 트랜잭션(Transaction) 이다.
트랜잭션이란
트랜잭션은 데이터베이스의 상태를 변화시키는 하나의 논리적 작업 단위다. 핵심은 “논리적”이라는 말이다. 물리적으로는 SQL 문 여러 개지만, 논리적으로는 쪼갤 수 없는 하나의 덩어리로 취급한다.
그래서 트랜잭션의 결과는 딱 두 가지뿐이다.
- 전부 반영된다 (Commit)
- 전부 취소된다 (Rollback)
“절반만 반영”은 존재하지 않는다. 이것이 트랜잭션의 존재 이유다.
트랜잭션의 상태
시험에 상태 전이가 종종 나온다. 5가지를 순서대로 기억하자.
| 상태 | 설명 |
|---|---|
| 활동(Active) | 트랜잭션이 실행 중인 상태 |
| 부분 완료(Partially Committed) | 마지막 연산까지 실행했지만 아직 커밋 전 |
| 완료(Committed) | 커밋이 끝나 결과가 영구 반영된 상태 |
| 실패(Failed) | 오류로 더 이상 실행할 수 없는 상태 |
| 철회(Aborted) | 롤백되어 트랜잭션 이전 상태로 되돌아간 상태 |
흐름은 이렇다.
활동 ──> 부분 완료 ──> 완료
│ │
└──> 실패 <─┘
│
└──> 철회 (Rollback)
주의할 점 하나. 부분 완료에서도 실패로 갈 수 있다. 마지막 연산까지 다 했어도 커밋 도중 디스크 오류가 나면 실패 상태로 떨어진다. “부분 완료 = 성공 확정”이 아니다.
ACID 4원칙
트랜잭션이 보장해야 할 네 가지 성질. 정처기 단골 문제다.
A — 원자성(Atomicity)
전부 수행되거나, 전혀 수행되지 않아야 한다. All or Nothing.
앞의 계좌 이체에서 출금만 되고 입금이 실패했다면, 출금도 되돌려야 한다. 이 되돌리기를 담당하는 것이 Rollback이고, 되돌릴 정보를 기록해 두는 것이 Undo 로그다.
C — 일관성(Consistency)
트랜잭션 전후로 데이터베이스가 일관된 상태를 유지해야 한다.
여기서 “일관”은 데이터베이스에 정의된 제약조건이 계속 만족된다는 뜻이다. 예를 들어 “계좌 잔액은 0 이상”이라는 제약이 있다면, 트랜잭션이 끝난 시점에도 그 제약은 지켜져야 한다. 이체 전 총액과 이체 후 총액이 같아야 한다는 것도 일관성의 예다.
원자성과 헷갈리기 쉬운데, 구분은 이렇다.
- 원자성: 연산의 묶음에 대한 보장 (다 하거나 안 하거나)
- 일관성: 데이터의 상태에 대한 보장 (규칙을 깨지 않는다)
I — 격리성(Isolation, 독립성)
둘 이상의 트랜잭션이 동시에 실행될 때, 서로의 중간 결과를 참조할 수 없다.
내 트랜잭션이 진행 중인 미완성 데이터를 남이 읽어 가면, 내가 롤백했을 때 남은 존재하지 않는 값으로 계산을 끝내 버린다. 이걸 막는 것이 격리성이고, 구현 수단이 잠금(Locking) 이다.
D — 지속성(Durability, 영속성)
성공적으로 완료된 트랜잭션의 결과는 영구적으로 반영되어야 한다.
커밋 응답을 받은 뒤라면, 그 직후 정전이 나도 데이터는 살아 있어야 한다. 이를 위해 DBMS는 변경 사항을 데이터 파일에 쓰기 전에 로그(Redo 로그)에 먼저 기록한다. 재시작할 때 로그를 보고 못다 쓴 변경을 다시 적용한다.
암기 팁: 원자성-Rollback, 지속성-Redo 로그, 격리성-Locking. 원칙과 구현 수단을 짝지어 외우면 응용 문제에서 흔들리지 않는다.
격리 수준과 이상 현상
격리성을 100% 지키면 안전하지만 느리다. 모든 트랜잭션을 한 줄로 세워야 하기 때문이다. 그래서 실무에서는 성능과 안전성을 저울질해 격리 수준(Isolation Level) 을 고른다.
먼저 격리가 약할 때 생기는 이상 현상 3가지부터.
| 현상 | 내용 |
|---|---|
| Dirty Read | 커밋되지 않은 값을 읽는다. 그 트랜잭션이 롤백하면 읽은 값은 유령이 된다 |
| Non-Repeatable Read | 같은 행을 두 번 읽었는데 값이 다르다. 사이에 남이 UPDATE 했다 |
| Phantom Read | 같은 조건으로 두 번 조회했는데 없던 행이 나타난다. 사이에 남이 INSERT 했다 |
Non-Repeatable Read와 Phantom Read의 차이는 행의 값이 바뀐 것(UPDATE)이냐, 행의 개수가 바뀐 것(INSERT)이냐다. 이 구분을 묻는 문제가 자주 나온다.
이제 격리 수준 4가지.
| 수준 | Dirty | Non-Repeatable | Phantom |
|---|---|---|---|
| Read Uncommitted | 발생 | 발생 | 발생 |
| Read Committed | 방지 | 발생 | 발생 |
| Repeatable Read | 방지 | 방지 | 발생 |
| Serializable | 방지 | 방지 | 방지 |
아래로 갈수록 안전하고, 아래로 갈수록 느리다. 이 표는 통째로 외워 두면 객관식에서 바로 답이 보인다.
정리
- 트랜잭션은 논리적 작업 단위이며, 결과는 커밋 아니면 롤백뿐이다.
- 상태는 활동 → 부분 완료 → 완료, 갈라지면 실패 → 철회.
- ACID: 원자성(All or Nothing), 일관성(제약조건 유지), 격리성(중간 결과 차단), 지속성(결과 영구 반영).
- 격리 수준은 안전성과 성능의 트레이드오프이고, 낮을수록 Dirty / Non-Repeatable / Phantom Read가 순서대로 튀어나온다.
시험에서는 “다음 중 트랜잭션의 특성이 아닌 것은?” 같은 형태로 ACID를 직접 묻거나, 상황을 던져 놓고 “위반된 특성은?”을 묻는다. 네 원칙을 각각 한 문장으로 말할 수 있는지를 기준으로 점검해 보자.
이 글에 대한 의견은 아래 댓글로 남겨주세요 (GitHub 계정 필요). 로그인 없이 남기고 싶다면