데이터베이스 엔지니어링 마스터 노트 시리즈 1편. ACID 4속성의 정확한 의미, 트랜잭션 생명주기(BEGIN→COMMIT/ROLLBACK), 4가지 격리 수준(Read Uncommitted~Serializable)과 발생하는 이상현상(Dirty Read·Non-Repeatable Read·Phantom Read), WAL의 내구성 보장 메커니즘까지 — DB 백엔드 엔지니어의 가장 단단한 토대를 한 흐름으로.
이 글은 데이터베이스 엔지니어링 마스터 노트 시리즈의 첫 번째 편입니다. 백엔드 개발자라면 매일 만지는 게 DB. 그런데 트랜잭션 격리 수준 4종 차이를 즉답 가능? Read Committed가 PostgreSQL 기본인지, MySQL InnoDB 기본인지? 이 정도면 그 차이가 운영 사고를 가르는 자리예요.
이 시리즈 8편은 ACID·인덱싱·파티셔닝·샤딩·복제·동시성·내부 구조·고급 주제를 다룹니다. 1편의 목표 — ACID 4속성과 격리 수준 4단계 를 손에 잡히게 만들기.
이 시리즈는 PostgreSQL·MySQL·SQLite 공식 문서, Designing Data-Intensive Applications, 여러 DB 엔지니어링 학습 자료를 참고해 한국어 학습 노트로 풀어쓴 자료입니다.
읽으면서 로컬에 PostgreSQL을 띄우고 두 터미널에서 직접 트랜잭션을 동시 실행해 보면 본문이 머리에 훨씬 잘 박혀요. 30분이면 첫 격리 수준 차이를 눈으로 확인할 수 있습니다.
트랜잭션 — 논리적 작업 단위
Logical Unit of Work — 여러 SQL을 하나의 단위로 묶음. 전부 성공 OR 전부 실패.
생명주기
BEGIN; -- 시작
SELECT balance FROM accounts WHERE id = 1; -- 잔액 확인
UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- 출금
UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- 입금
COMMIT; -- 영구 저장
-- 또는
-- ROLLBACK; -- 모두 취소
상태 4단계
| 상태 | 의미 |
|---|---|
| BEGIN | 트랜잭션 시작 |
| 진행 중 | 쿼리 실행 (변경은 메모리) |
| COMMIT | 디스크 영구 저장 |
| ROLLBACK | 모든 변경 취소 |
여기서 시험 함정이 하나 있어요. 단일 쿼리도 자동 트랜잭션입니다. UPDATE orders SET status = 'shipped', shipped_at = NOW() — 두 컬럼 모두 업데이트 OR 모두 X. 명시적 BEGIN 없어도 원자적.
ACID 4속성
| 속성 | 한 줄 |
|---|---|
| Atomicity | 전부 또는 전무 (All or Nothing) |
| Consistency | 데이터 무결성 유지 |
| Isolation | 트랜잭션 간 독립성 |
| Durability | 커밋된 데이터의 영구성 |
1. Atomicity — 원자성
원자(Atom) — 더 이상 쪼갤 수 없는 단위. 트랜잭션의 모든 쿼리는 전부 성공 OR 전부 실패.
BEGIN;
UPDATE accounts SET balance = balance - 500 WHERE user_id = 1;
-- 시스템 크래시!
UPDATE accounts SET balance = balance + 500 WHERE user_id = 2;
COMMIT;
-- 결과: 첫 UPDATE도 자동 롤백 → 둘 다 안 됨
부분 성공(Partial Success) 절대 허용 X. 이게 계좌 이체 같은 작업의 신뢰성 토대.
2. Isolation — 격리성
여러 트랜잭션이 동시 실행될 때 서로의 중간 상태를 못 봄.
격리 없이 발생하는 4가지 이상현상
Dirty Read
T1: UPDATE balance = 500 (아직 커밋 X)
T2: SELECT balance → 500 읽음 (커밋 안 된 값!)
T1: ROLLBACK (취소)
→ T2가 읽은 500은 실제 존재하지 않은 값
Non-Repeatable Read
T1: SELECT balance → 1000
T2: UPDATE balance = 500, COMMIT
T1: SELECT balance → 500 (같은 트랜잭션에서 다른 값!)
Phantom Read
T1: SELECT COUNT(*) FROM users WHERE age > 20 → 10
T2: INSERT INTO users (age) VALUES (25), COMMIT
T1: SELECT COUNT(*) FROM users WHERE age > 20 → 11 (유령 출현!)
행 자체가 새로 생기는 게 차이점. Non-Repeatable Read는 기존 행 값 변경.
Lost Update
T1: SELECT balance → 1000
T2: SELECT balance → 1000
T1: UPDATE balance = 1000 + 100 (1100)
T2: UPDATE balance = 1000 + 200 (1200) ← T1 변경 덮어씀
4가지 격리 수준
| 격리 수준 | Dirty | Non-Repeatable | Phantom |
|---|---|---|---|
| Read Uncommitted | ◯ | ◯ | ◯ |
| Read Committed | X | ◯ | ◯ |
| Repeatable Read | X | X | ◯ |
| Serializable | X | X | X |
여기서 정말 중요한 시험 함정 — PostgreSQL 기본은 Read Committed, MySQL InnoDB 기본은 Repeatable Read. DB마다 다름.
격리 수준 변경
-- PostgreSQL
BEGIN ISOLATION LEVEL SERIALIZABLE;
-- MySQL
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
여기서 정말 중요한 시험 함정 — PostgreSQL의 Repeatable Read는 사실상 Snapshot Isolation 입니다. SQL 표준 RR보다 강해서 Phantom Read도 방지. 다만 SSI(Serializable Snapshot Isolation) 수준은 Serializable이라야.
격리 수준 trade-off
높은 격리 ↑ 안전 ↑ 동시성 ↓
낮은 격리 ↓ 안전 ↓ 동시성 ↑
운영 결정 — 대부분 Read Committed 충분. 금융·정확도 critical = Serializable.
3. Consistency — 일관성
데이터의 무결성 규칙(Constraint·Trigger·외래 키)을 트랜잭션 전후로 유지.
-- UNIQUE 제약 위반 시도
INSERT INTO users (email) VALUES ('alice@example.com'); -- 이미 존재
-- → 자동 거부, 일관성 유지
여기서 시험 함정이 하나 있어요. ACID의 C와 CAP의 C는 다른 의미입니다. ACID-C = 무결성 규칙. CAP-C = 분산 시스템 일관성 (모든 노드 같은 값).
결과적 일관성 (Eventual Consistency)
NoSQL·분산 DB의 모델. 시간 지나면 결국 일관됨. 즉시 X.
Master에 쓰기 → Replica에 비동기 복제
짧은 시간 동안 Master ≠ Replica
시간 지나면 동기화
8편(고급)에서 자세히.
4. Durability — 내구성
커밋된 데이터는 시스템 크래시·정전에도 살아남음.
WAL — Write-Ahead Log
1. 변경 사항을 먼저 WAL(로그 파일)에 기록
2. WAL fsync → 디스크 강제 쓰기
3. 사용자에게 "COMMIT 성공" 응답
4. 나중에 메모리 → 데이터 파일 비동기 쓰기
크래시 후 재시작 시 — WAL을 재생(replay)해서 마지막 커밋 상태로 복구.
여기서 정말 중요한 시험 함정 — fsync 없이는 내구성 X. RAM에만 있으면 정전 시 손실. PostgreSQL synchronous_commit = on 이 기본 — 매 커밋 시 fsync.
Asynchronous Commit (성능 vs 안전)
-- PostgreSQL
SET synchronous_commit = off;
-- COMMIT 즉시 응답, fsync는 비동기
-- 정전 시 마지막 ~600ms 분량 손실 가능
성능 우선 워크로드에서 trade-off로 사용.
SERIALIZABLE — 가장 강한 격리
직렬 실행과 동일한 결과 보장. 모든 이상현상 방지.
구현 방식 2가지
| 방식 | DB |
|---|---|
| 2PL (Two-Phase Locking) | 전통 |
| SSI (Serializable Snapshot Isolation) | PostgreSQL 9.1+ |
SSI는 낙관적 — 충돌 감지 시 한쪽 트랜잭션 abort.
-- 충돌 시
ERROR: could not serialize access due to read/write dependencies
-- → 애플리케이션이 retry해야
여기서 시험 함정이 하나 있어요. SSI는 retry 로직 필수. 트랜잭션 abort가 정상 동작이라 애플리케이션이 자동 재시도.
시험 직전 한 번 더 — 자주 헷갈리는 함정 모음
여기까지가 1편의 핵심입니다. 시험 직전 또는 실무에서 헷갈릴 때 다시 펼쳐 볼 수 있게 압축 노트로 마무리할게요.
- 트랜잭션 = Logical Unit of Work
- 생명주기 — BEGIN → 진행 중 → COMMIT / ROLLBACK
- 단일 쿼리도 자동 트랜잭션 (원자적)
- ACID — Atomicity / Consistency / Isolation / Durability
- Atomicity = All or Nothing, 부분 성공 X
- 4 이상현상 — Dirty Read / Non-Repeatable Read / Phantom Read / Lost Update
- Phantom Read = 행 자체가 생김 (Non-Repeatable는 기존 행 값 변경)
- 격리 수준 4 — Read Uncommitted / Read Committed / Repeatable Read / Serializable
- PostgreSQL 기본 = Read Committed
- MySQL InnoDB 기본 = Repeatable Read
- PostgreSQL Repeatable Read ≈ Snapshot Isolation (Phantom 방지)
- 격리↑ = 안전↑ + 동시성↓
- 대부분 Read Committed 충분, 금융 = Serializable
- ACID-C ≠ CAP-C
- ACID-C = 무결성 규칙 (Constraint)
- CAP-C = 모든 노드 같은 값
- Eventual Consistency = NoSQL·분산 모델
- Durability = WAL + fsync
- 크래시 후 WAL replay로 복구
synchronous_commit = off= 성능 vs 안전 trade-off- Serializable — 2PL 또는 SSI
- SSI = 낙관적, 충돌 시 abort, retry 필수
시리즈 다른 편
1편 — ACID·트랜잭션 (현재 글)
1편 — ACID·트랜잭션 (현재 글)
2편 — 인덱싱 (B-Tree·B+Tree·Bloom Filter)
3편 — 파티셔닝
4편 — 샤딩
5편 — 복제 (Replication)
6편 — 동시성 제어 (Lock·MVCC)
7편 — DB 내부 구조 (저장소 엔진)
8편 — 고급 주제 (NoSQL·CAP·LSM Tree)
공식 문서: PostgreSQL Transaction Isolation / MySQL Transaction Isolation 에서 더 깊이.
다음 글(2편)에서는 인덱싱 본격 — B-Tree vs B+Tree, Primary vs Secondary Index, EXPLAIN으로 쿼리 분석, Bloom Filter·UUID 인덱스 함정까지 풀어 갑니다.