데이터베이스 트랜잭션과 격리 수준 완벽 정리
T1: read ------ update ------ commit
T2: read ------ read ------ commit
Isolation level decides:
- can T2 see uncommitted data?
- can T2 read different values twice?
- can new rows appear between reads?
트랜잭션 격리 수준은 추상적인 이론보다, 동시에 실행되는 두 트랜잭션 사이에서 어떤 일이 허용되는지로 이해하면 훨씬 쉽습니다. Dirty Read, Non-Repeatable Read, Phantom Read는 모두 이 겹치는 시간 구간에서 어떤 관측이 가능한지의 차이입니다.
ACID 속성
| 속성 | 설명 |
|---|---|
| Atomicity | 트랜잭션은 전부 성공하거나 전부 실패 |
| Consistency | 트랜잭션 전후 데이터 무결성 유지 |
| Isolation | 동시 트랜잭션이 서로 영향을 주지 않음 |
| Durability | 커밋된 데이터는 영구 저장 |
격리 수준별 문제
| 격리 수준 | Dirty Read | Non-Repeatable Read | Phantom Read |
|---|---|---|---|
| READ UNCOMMITTED | 발생 | 발생 | 발생 |
| READ COMMITTED | 없음 | 발생 | 발생 |
| REPEATABLE READ | 없음 | 없음 | 발생 |
| SERIALIZABLE | 없음 | 없음 | 없음 |
MySQL InnoDB 기본값: REPEATABLE READ
Dirty Read
-- 트랜잭션 A
BEGIN;
UPDATE accounts SET balance = 0 WHERE id = 1;
-- 아직 커밋 안 함
-- 트랜잭션 B (READ UNCOMMITTED)
SELECT balance FROM accounts WHERE id = 1;
-- 0을 읽음 → A가 롤백하면 잘못된 값
Non-Repeatable Read
-- 트랜잭션 A
BEGIN;
SELECT balance FROM accounts WHERE id = 1; -- 1000 읽음
-- 트랜잭션 B
UPDATE accounts SET balance = 2000 WHERE id = 1;
COMMIT;
-- 트랜잭션 A (같은 트랜잭션에서 재조회)
SELECT balance FROM accounts WHERE id = 1; -- 2000 읽음 (달라짐!)
Phantom Read
-- 트랜잭션 A
BEGIN;
SELECT * FROM orders WHERE amount > 1000; -- 3건
-- 트랜잭션 B
INSERT INTO orders (amount) VALUES (5000);
COMMIT;
-- 트랜잭션 A
SELECT * FROM orders WHERE amount > 1000; -- 4건! (유령 행 등장)
MySQL 격리 수준 설정
-- 세션 레벨
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 글로벌 레벨
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 현재 격리 수준 확인
SELECT @@transaction_isolation;
낙관적 vs 비관적 락
-- 비관적 락 (SELECT FOR UPDATE)
BEGIN;
SELECT * FROM inventory WHERE id = 1 FOR UPDATE;
UPDATE inventory SET stock = stock - 1 WHERE id = 1;
COMMIT;
-- 낙관적 락 (version 컬럼)
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 1 AND version = 5;
-- affected rows = 0이면 충돌 → 재시도
운영 환경에서 어려워지는 지점
- 트랜잭션 격리 수준은 학술 설정값이 아니라 동시성 하의 정확성, 지연, 경쟁을 직접 바꾸는 운영 결정이다.
- 팀은 기본 격리 수준을 그대로 두고, 어떤 이상 현상이 허용되는지 공통 모델 없이 버그를 디버깅하는 경우가 많다.
- 더 강한 격리는 정확성을 지켜주지만 접근 패턴 설계가 없으면 처리량을 조용히 깎을 수 있다.
중요한 아키텍처 결정
- 더 강한 격리의 위신이 아니라 반드시 지켜야 하는 비즈니스 invariant에서 시작한다.
- read-modify-write 경로의 경쟁 구간을 줄이도록 설계한다.
- 격리 수준 선택과 함께 lock, retry, idempotency 정책을 맞춘다.
실무 예시
올바른 질문은 보통 “여기서 어떤 이상 현상이 허용 불가인가?”다.
재고 예약
초과 판매를 막아야 함
workload에 따라 row lock 또는 optimistic retry 필요
피해야 할 안티패턴
- 기본 격리 수준이 어떤 이상 현상을 허용하는지 모른 채 사용하는 것.
- 문제가 몇 개 workflow에만 있는데도 전체 격리를 올리는 것.
- 더 강한 동시성 제어를 도입하고도 retry 설계를 무시하는 것.
운영 체크리스트
- 금전, 재고, 정렬/순서 workflow의 invariant를 문서화한다.
- 정상 경로 지연뿐 아니라 경쟁 시나리오를 부하 테스트한다.
- deadlock과 retry 빈도를 측정한다.
- lock 범위와 트랜잭션 길이를 검토한다.
최종 판단
격리 수준은 정확성 예산 결정이다. 가장 좋은 수준은 실제로 중요한 invariant를 지키면서도 가장 낮은 비용을 가지는 수준이다.
Continue Reading
다음으로 읽기 좋은 글
MySQL 인덱스 최적화 전략 — EXPLAIN으로 쿼리 분석하기
MySQL 인덱스의 동작 원리와 최적화 전략을 EXPLAIN 분석과 함께 정리합니다. 복합 인덱스, 커버링 인덱스, 인덱스 힌트까지 실무 예제로 알아봅니다.
🗄️ Database데이터베이스 파티셔닝 전략 — 샤딩, 수평/수직 분할
대용량 데이터를 효율적으로 관리하기 위한 파티셔닝 전략을 정리합니다. MySQL 파티션, 수평/수직 분할, 샤딩의 개념과 구현 방법을 알아봅니다.
📈 최신 동향PostgreSQL 18 최신 동향: 실무에서 진짜 중요한 변화
PostgreSQL 18은 단순 업그레이드 뉴스가 아닙니다. AIO, skip scan, 업그레이드 후 성능 회복, OAuth, generated columns까지 운영팀과 개발팀 모두에게 영향이 큰 변화가 들어왔습니다.
🚀 DevOpsKubernetes 심화 — HPA, Resource 관리, Pod Scheduling
Kubernetes 운영을 설정 모음이 아니라 자원 배치와 장애 복원력의 관점에서 정리합니다. requests/limits, HPA, affinity, taint, PDB, probe를 언제 어떻게 써야 하는지 실무적으로 설명합니다.
다음 탐색