DB 엔지니어링 — ACID·트랜잭션·격리 수준

2026-05-03확률과 통계 마스터 노트

데이터베이스 엔지니어링 마스터 노트 시리즈 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 필수

시리즈 다른 편

공식 문서: PostgreSQL Transaction Isolation / MySQL Transaction Isolation 에서 더 깊이.

다음 글(2편)에서는 인덱싱 본격 — B-Tree vs B+Tree, Primary vs Secondary Index, EXPLAIN으로 쿼리 분석, Bloom Filter·UUID 인덱스 함정까지 풀어 갑니다.

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!