학습 목표
동시 트랜잭션에서 발생하는 Lost Update·Dirty Read·Non-repeatable Read·Phantom을 구분할 수 있다.
격리 수준(Read Uncommitted ~ Serializable)이 허용·방지하는 이상을 설명할 수 있다.
2PL(Two-Phase Locking) 의 growing·shrinking phase를 설명할 수 있다.
실무에서 격리 수준 선택·비관적 락(SELECT FOR UPDATE) 방향을 잡을 수 있다.
문제 상황
- 재고 10개인데 동시 주문 두 건이 모두 성공했다
- 둘 다
qty=10을 읽고 각각 -1 — Lost Update
- 둘 다
- A 트랜잭션이 롤백했는데 B가 A가 쓴 미커밋 값을 읽었다
- Dirty Read
- 같은 트랜잭션에서
SELECT두 번인데 잔액이 바뀌어 있다- Non-repeatable Read
WHERE status='OPEN'5건인데 다시 읽으니 6건- Phantom Read
SERIALIZABLE로 올렸더니 데드락이 늘었다- 격리 강할수록 성능·교착 비용
앞서 ACID와 Isolation을 봤다. 이번 편은 동시에 돌아가는 트랜잭션이 서로 어떤 간섭을 일으키는지, DBMS가 어떤 수준으로 막는지다.
1. 왜 동시성 제어가 필요한가
여러 세션이 같은 행·같은 범위를 동시에 읽고 쓴다.
| 없으면 | 결과 |
|---|---|
| 읽기·쓰기 순서 미정 | 중간 상태 노출, 덮어쓰기 |
| 원자성만 | 단일 txn 내부는 안전 — 다른 txn과 충돌 |
앱에서만 synchronized |
DB 여러 연결·여러 서버 — DBMS가 조율 |
- 스케줄(schedule) — 트랜잭션 연산의 시간 순서
- 직렬성(serializability) — 동시 실행 결과가 어떤 순차 실행과 같으면 OK
- DBMS는 락·MVCC·격리 수준으로 스케줄을 제한 (MVCC 상세는 이후 편)
2. 동시성 이상 (Anomalies)

Lost Update (갱신 유실)
두 txn이 같은 값을 읽고 각각 쓰면 한쪽 갱신이 사라진다.
T1: READ balance=1000
T2: READ balance=1000
T1: WRITE balance=900 (-100)
T2: WRITE balance=800 (-200) ← T1 갱신 덮어씀
Dirty Read (더티 리드)
커밋되지 않은 다른 txn의 쓰기를 읽는다. 롤백 시 읽은 값이 무효.
Non-repeatable Read (반복 불가능 읽기)
같은 txn에서 같은 행을 두 번 읽는데 값이 다르다 (다른 txn 커밋 후).
Phantom Read (팬텀)
같은 조건으로 두 번 읽는데 행 개수가 다르다 (INSERT/DELETE 커밋 후).
| 이상 | 초점 | 다른 txn |
|---|---|---|
| Lost Update | 쓰기 충돌 | 둘 다 쓰기 |
| Dirty Read | 미커밋 읽기 | 읽기 vs 미커밋 쓰기 |
| Non-repeatable | 행 재조회 | 읽기 vs 커밋된 UPDATE |
| Phantom | 범위 재조회 | 읽기 vs 커밋된 INSERT/DELETE |
3. 격리 수준
SQL 표준 격리 수준 — 약할수록 동시성↑, 이상 허용↑.

| 격리 수준 | Dirty Read | Non-repeatable | Phantom | 비고 |
|---|---|---|---|---|
| Read Uncommitted | 가능 | 가능 | 가능 | 실무 거의 안 씀 |
| Read Committed | 불가 | 가능 | 가능 | PostgreSQL·Oracle 기본 |
| Repeatable Read | 불가 | 불가 | 가능* | MySQL InnoDB 기본 |
| Serializable | 불가 | 불가 | 불가 | 가장 엄격 |
* PostgreSQL Repeatable Read는 phantom도 방지 (SSI) — DB별 차이 주의
-- PostgreSQL
BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- ...
COMMIT;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
| DB | 기본 격리 |
|---|---|
| PostgreSQL | Read Committed |
| MySQL InnoDB | Repeatable Read |
| SQL Server | Read Committed |
- 격리 한 단계 올릴 때마다 락·버전 유지 비용 증가
- 대부분 Read Committed + 필요한 구간만 명시적 락
예시는 code/concurrency-examples.sql 참고.
4. Lost Update 방어 (실무)
| 방법 | 설명 |
|---|---|
| 비관적 락 | SELECT ... FOR UPDATE — 쓰기 전 행 잠금 |
| 낙관적 락 | UPDATE ... WHERE id=? AND version=? — 버전 불일치 시 재시도 |
| 원자 연산 | UPDATE SET qty = qty - 1 WHERE qty > 0 — read-modify-write 한 문장 |
BEGIN;
SELECT qty FROM inventory WHERE sku = 'ABC' FOR UPDATE;
-- app checks qty >= 1
UPDATE inventory SET qty = qty - 1 WHERE sku = 'ABC';
COMMIT;
-- optimistic: version column
UPDATE products
SET price = 1200, version = version + 1
WHERE id = 42 AND version = 7;
-- 0 rows → conflict, retry
FOR UPDATE— 데드락 가능 — 락 순서 통일, 짧은 txn- 낙관적 — 충돌 적을 때 유리, 앱 재시도 필요
5. 2PL 개요
Two-Phase Locking — 락을 두 단계로 운용하는 직렬성 보장 프로토콜.

| 단계 | 규칙 |
|---|---|
| Growing | 락 획득만 — S(shared)·X(exclusive) |
| Shrinking | 락 해제만 — 새 락 금지 |
| Strict 2PL | X 락을 COMMIT/ROLLBACK까지 유지 — Dirty Read 방지 |
| 락 | 호환 |
|---|---|
| S (shared) | 여러 txn 동시 읽기 |
| X (exclusive) | 쓰기 — 다른 S·X 불가 |
- 2PL은 데드락 가능 — DBMS 감지·희생자 선택 (데드락 편)
- MVCC는 읽기 락 없이 스냅샷 — PostgreSQL Read Committed + MVCC
6. 실무 선택 가이드
| 상황 | 권장 |
|---|---|
| 일반 CRUD | Read Committed (DB 기본) |
| 리포트·집계 같은 txn | Repeatable Read 또는 스냅샷 export |
| 재고·좌석·잔액 | FOR UPDATE 또는 원자 UPDATE |
| 전역 불변식 | Serializable — 성능·데드락 감수 |
| ORM | @Transactional(isolation=...) — 기본값 확인 |
-- lock wait (PostgreSQL)
SELECT pid, wait_event_type, wait_event, query
FROM pg_stat_activity
WHERE wait_event_type = 'Lock';
7. 정리
- 동시 txn 간섭 — Lost Update, Dirty Read, Non-repeatable, Phantom
- 격리 수준으로 허용 이상 트레이드오프 — DB 기본값 확인
- Lost Update는 FOR UPDATE·version·원자 UPDATE로 방어
- 2PL — growing/shrinking, Strict 2PL은 X 락을 commit까지
- MVCC·데드락·WAL은 이후 편
다음에 다룰 것
- 데드락과 타임아웃
- 대기 그래프, lock timeout
해당 내용은 Database System Concepts, 7/E (Avraham Silberschatz, Henry F. Korth, S. Sudarshan) 의 내용을 기반으로 합니다.
'데이터베이스 따라잡기' 카테고리의 다른 글
| 로깅과 복구 (0) | 2026.06.29 |
|---|---|
| 데드락과 타임아웃 (0) | 2026.06.28 |
| 트랜잭션과 ACID (0) | 2026.06.26 |
| 실행 계획 읽기 (0) | 2026.06.25 |
| 인덱스 구조 (0) | 2026.06.24 |