데이터베이스 따라잡기

동시성 제어

meellon 2026. 6. 27. 05:30

학습 목표

동시 트랜잭션에서 발생하는 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로 올렸더니 데드락이 늘었다
    • 격리 강할수록 성능·교착 비용

앞서 ACIDIsolation을 봤다. 이번 편은 동시에 돌아가는 트랜잭션이 서로 어떤 간섭을 일으키는지, 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