TestForge | Aidevops | 📊 Plogger ✍️ Blog 📚 Docs
plogger

AI DevOps Korea

AI 서비스 개발, 운영, 성능개선을 하나의 루프로 연결합니다

aidevops.kr에서 LLMOps, RAG, AI Agent, 관측성, 평가, 비용-성능 최적화를 실전 운영 관점으로 정리합니다.

데이터베이스 트랜잭션과 격리 수준 완벽 정리

데이터베이스 트랜잭션과 격리 수준 완벽 정리 다이어그램
이 글에서 다루는 핵심 흐름, 아키텍처 구조, 주요 판단 포인트를 한눈에 이해할 수 있도록 정리한 그림입니다.
## 그림으로 보는 구조
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 ReadNon-Repeatable ReadPhantom 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

다음으로 읽기 좋은 글

다음 탐색

이 주제를 시스템 관점으로 더 이어서 보기