F-LAB JAVA · 6주차 · Phase 6 · 트랜잭션과 ACID
★ 깊이 파기 — ACID 의 세 번째, 면접 단골
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
Isolation (격리성) 은 동시에 실행되는 트랜잭션들이 서로 독립적으로 보이도록 격리하는 성질로, A 계좌 1만원에서 두 트랜잭션이 동시에 6천원씩 인출하면 격리성이 보장되어야 한쪽이 실패하며, 격리 수준 4단계 (READ UNCOMMITTED ~ SERIALIZABLE) 가 Dirty Read · Non-repeatable Read · Phantom Read 같은 이상 현상을 단계별로 차단한다.
Isolation (격리성) 은 동시에 실행되는 트랜잭션들이 마치 혼자만 실행되는 것처럼 보이도록 격리하는 성질이다.
예시 — A 계좌 1만원, 트랜잭션1: A → B 로 6천원 이체, 트랜잭션2: A → C 로 6천원 이체 (동시).
격리성이 없다면 둘 다 "A 잔액 1만원" 을 읽고 둘 다 6천원 차감 → A 잔액 -2천원 (불가능!) 이지만, 격리성이 보장되면 한쪽이 대기하거나 실패 한다.
격리 수준은 4단계 — READ UNCOMMITTED → READ COMMITTED → REPEATABLE READ → SERIALIZABLE 로 갈수록 안전하고 느려지며, 각 단계는 Dirty Read (commit 전 읽기), Non-repeatable Read (같은 행 두 번 읽기 다름), Phantom Read (범위 조회 시 새 행 등장) 를 단계별로 차단한다.
Isolation = 도서관 칸막이:
격리 없음:
- 모두 한 책상 (한 책 공유)
- 누가 뭐 쓰는지 다 봄
- 충돌 자주
격리 있음 (칸막이):
- 각자 칸 (트랜잭션)
- 다른 사람 작업 안 보임
- 동시 진행 가능
A 1만원 시나리오:
- A 잔액 = 1만 (책)
- 사람1: 6천 인출 시도 (책 가져감)
- 사람2: 6천 인출 시도 (책 안 보임 — 격리)
→ 사람2는 대기 또는 실패
→ 잔액 -2천 (불가능) X
격리 수준 4단계:
1. READ UNCOMMITTED (칸막이 거의 X)
2. READ COMMITTED (커밋된 것만)
3. REPEATABLE READ (반복 동일)
4. SERIALIZABLE (완전 격리)
높을수록 안전, 느림
이상 현상:
- Dirty Read: 커밋 안 된 것 봄
- Non-repeatable: 같은 행 두 번 다름
- Phantom: 새 행 등장
→ Isolation = 트랜잭션 독립 (격리 수준 4단계), 이상 현상 단계별 차단.
1. Isolation 정의
2. A 1만원 시나리오
3. 격리 수준 4단계
4. Dirty Read
5. Non-repeatable Read
6. Phantom Read
7. 격리 수준별 차단
8. 높은 격리의 문제
9. Spring 의 격리 설정
Isolation (격리성):
동시 트랜잭션이:
- 서로 영향 X
- 독립적 실행처럼
- 격리됨
두 가지 의미:
1. 직렬화 (Serializable):
- 동시 실행이지만
- 순차 실행처럼 보임
2. 격리 수준:
- 어디까지 격리할지
- 4단계 트레이드오프
동시성 vs 일관성:
격리 높음:
- 일관성 ↑
- 동시성 ↓ (대기 ↑)
격리 낮음:
- 동시성 ↑
- 일관성 ↓ (이상)
→ 트레이드오프
Isolation (ILIC)
ILIC 시나리오:
- 운영자 A: 운임 조회 중
- 운영자 B: 같은 운임 수정 중
격리 없으면:
- A 가 B 의 미커밋 값 봄
- 잘못된 정보로 작업
격리 있으면:
- A 는 이전 값 (커밋된 것)
- B 가 commit 해야 A 도 봄
- 일관성
→ 멀티 사용자 환경 필수
Isolation (격리성) 의 정의는?
답:
1. 정의:
의미:
트레이드오프:
목적:
A 계좌 1만원:
트랜잭션1: A → B 6천 이체
트랜잭션2: A → C 6천 이체
동시 실행 시?
격리 없으면:
트랜잭션1: A 잔액 읽음 = 10000
트랜잭션2: A 잔액 읽음 = 10000 (동시)
T1: A = 10000 - 6000 = 4000
T2: A = 10000 - 6000 = 4000
최종: A = 4000 (12000 차감인데?)
→ 잔액 -2000 효과 (분실 갱신)
격리 있으면:
T1: A 잔액 락 + 읽음 = 10000
T2: A 잔액 락 대기 (T1 끝까지)
T1: A = 4000, B + 6000, commit
T2: 진행
- A 잔액 = 4000 (새로 읽음)
- 4000 < 6000 → 잔액 부족 실패
→ 정확
Lost Update (분실 갱신):
두 트랜잭션의 갱신:
- 한쪽이 다른쪽 덮어씀
- 갱신 손실
- 격리 부족 결과
→ 이상 현상의 하나
-- A 1만원 시나리오 (ILIC 재고 차감)
-- 격리 없으면 (위험)
-- T1, T2 동시
-- stock = 10
-- T1: SELECT stock FROM items WHERE id = 1; → 10
-- T2: SELECT stock FROM items WHERE id = 1; → 10
-- T1: UPDATE items SET stock = 10 - 5 = 5;
-- T2: UPDATE items SET stock = 10 - 5 = 5;
-- → 5 (10 차감인데 5?)
-- → Lost Update
-- 격리 있으면 (FOR UPDATE)
-- T1: SELECT stock FROM items WHERE id = 1 FOR UPDATE; → 10 (락)
-- T2: SELECT stock FROM items WHERE id = 1 FOR UPDATE; → 대기
-- T1: UPDATE items SET stock = 5; COMMIT;
-- T2: 진행 (stock=5 새로 읽음)
-- 5 < 5? → 처리
-- → 정확
A 1만원 시나리오 격리성은?
답:
1. 시나리오:
격리 없으면:
격리 있으면:
현상:
격리 수준 4단계:
1. READ UNCOMMITTED (가장 낮음)
2. READ COMMITTED
3. REPEATABLE READ
4. SERIALIZABLE (가장 높음)
→ ANSI SQL 표준
동시성 vs 안전:
낮음 (RU):
- 동시성 ↑
- 모든 이상 허용
높음 (SER):
- 동시성 ↓
- 모든 이상 차단
DB 기본값:
MySQL InnoDB: REPEATABLE READ
PostgreSQL: READ COMMITTED
Oracle: READ COMMITTED
SQL Server: READ COMMITTED
→ DB 별 기본 다름
-- MySQL 격리 수준 설정
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 또는
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- ILIC MySQL (기본 REPEATABLE READ)
-- 확인
SELECT @@TRANSACTION_ISOLATION;
-- "REPEATABLE-READ" (기본)
-- 트랜잭션별 변경 (필요 시)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 일부 분석 쿼리에 (Phantom 허용)
-- 보통 ILIC 은 기본 REPEATABLE READ 사용
-- 충분한 격리 + 적절한 성능
격리 수준 4가지는?
답:
1. 4단계:
트레이드오프:
MySQL 기본:
설정:
Dirty Read:
커밋 안 된 데이터 읽기:
- T1 이 UPDATE (미커밋)
- T2 가 그 값 읽음
- T1 이 rollback 하면?
- T2 는 존재 안 한 값 봤음
시나리오:
T1: UPDATE accounts SET balance = 5000 WHERE id = 'A';
-- (아직 미커밋)
T2: SELECT balance FROM accounts WHERE id = 'A';
-- 5000 읽음 (커밋 전)
T1: ROLLBACK;
-- A 의 balance 는 다시 원래대로
→ T2 는 존재하지 않은 값을 본 셈
→ Dirty Read
Dirty Read 위험:
- 잘못된 데이터로 결정
- 비즈니스 오류
- "유령 데이터"
→ 매우 위험
차단:
READ UNCOMMITTED: 허용 (위험)
READ COMMITTED 이상: 차단
→ 대부분 DB 기본은 RC 이상
Dirty Read 시나리오 (ILIC)
-- 운영자 A: 운임 수정 중
T1: UPDATE freights SET amount = 5000 WHERE shipment_id = 1;
-- 미커밋 (아직 결정 중)
-- 회계 직원 B: 보고서 작성
T2: SELECT amount FROM freights WHERE shipment_id = 1;
-- READ UNCOMMITTED 면? → 5000 (커밋 전!)
-- READ COMMITTED 면? → 이전 값 (안전)
-- A 가 rollback 결정
T1: ROLLBACK;
-- B 의 보고서:
-- READ UNCOMMITTED 면: 5000 으로 잘못 보고
-- → Dirty Read 사고
→ ILIC 같은 B2B 는 RC 이상 필수
Dirty Read 란?
답:
1. Dirty Read:
시나리오:
위험:
차단:
Non-repeatable Read:
같은 트랜잭션 안에서:
- 같은 행 두 번 조회
- 두 결과 다름
- "반복 불가"
시나리오:
T1: SELECT balance FROM accounts WHERE id = 'A';
-- 10000 읽음
T2: UPDATE accounts SET balance = 5000 WHERE id = 'A';
T2: COMMIT;
T1: SELECT balance FROM accounts WHERE id = 'A';
-- 5000 읽음 (이전과 다름!)
-- → Non-repeatable Read
문제:
T1 의 연속 조회:
- 첫 번째: 10000
- 두 번째: 5000
- 같은 트랜잭션 안에서 값 변화
→ 일관성 깨짐
차단:
READ UNCOMMITTED: 발생
READ COMMITTED: 발생
REPEATABLE READ: 차단 (이름의 유래)
SERIALIZABLE: 차단
Non-repeatable Read (ILIC)
-- T1: 배송 정보 분석 (긴 트랜잭션)
T1: SELECT weight FROM shipments WHERE id = 1;
-- 100kg 읽음
-- T2: 동시에 수정
T2: UPDATE shipments SET weight = 150 WHERE id = 1;
T2: COMMIT;
-- T1: 다시 조회 (분석 위해)
T1: SELECT weight FROM shipments WHERE id = 1;
-- READ COMMITTED: 150 (다름!) → Non-repeatable
-- REPEATABLE READ: 100 (같음)
-- T1 의 분석:
-- 같은 트랜잭션 안에서 weight 가 변하면 분석 일관성 X
-- → REPEATABLE READ 필요
→ ILIC 기본 (MySQL RR) 이 적절
Non-repeatable Read 란?
답:
1. Non-repeatable:
시나리오:
문제:
차단:
Phantom Read:
같은 트랜잭션 안에서:
- 범위 조회 두 번
- 새 행 등장 (또는 사라짐)
- "유령 행"
시나리오:
T1: SELECT COUNT(*) FROM orders WHERE amount > 100;
-- 10건
T2: INSERT INTO orders (amount) VALUES (500);
T2: COMMIT;
T1: SELECT COUNT(*) FROM orders WHERE amount > 100;
-- 11건 (새 행 등장!)
-- → Phantom Read
Non-repeatable vs Phantom:
Non-repeatable:
- 기존 행 값 변화
Phantom:
- 새 행 등장 / 사라짐
- 범위 (WHERE) 결과 변화
→ 둘 다 일관성 깨짐
차단:
READ UNCOMMITTED: 발생
READ COMMITTED: 발생
REPEATABLE READ: 발생 (표준), 차단 (InnoDB)
SERIALIZABLE: 차단
→ ANSI 표준은 RR 에서 발생
→ MySQL InnoDB 는 RR 에서도 차단 (갭 락)
Phantom Read (ILIC)
-- T1: 통계 분석
T1: SELECT COUNT(*) FROM shipments
WHERE status = 'BOOKED' AND created_at > '2026-01-01';
-- 100건
-- T2: 새 예약
T2: INSERT INTO shipments (status, created_at)
VALUES ('BOOKED', NOW());
T2: COMMIT;
-- T1: 다시 통계
T1: SELECT COUNT(*) FROM shipments
WHERE status = 'BOOKED' AND created_at > '2026-01-01';
-- READ COMMITTED: 101건 (Phantom!)
-- MySQL REPEATABLE READ (InnoDB): 100건 (갭 락)
-- SERIALIZABLE: 100건
-- ILIC 의 분석 일관성:
-- → MySQL InnoDB REPEATABLE READ 가 충분
Phantom Read 란?
답:
1. Phantom:
시나리오:
vs Non-repeatable:
차단:
| 격리 수준 | Dirty Read | Non-repeatable | Phantom |
|---|---|---|---|
| READ UNCOMMITTED | ⚠️ 발생 | ⚠️ 발생 | ⚠️ 발생 |
| READ COMMITTED | ✅ 차단 | ⚠️ 발생 | ⚠️ 발생 |
| REPEATABLE READ | ✅ 차단 | ✅ 차단 | ⚠️ 발생* |
| SERIALIZABLE | ✅ 차단 | ✅ 차단 | ✅ 차단 |
* MySQL InnoDB 는 REPEATABLE READ 에서도 Phantom 차단 (갭 락)
READ UNCOMMITTED:
- 모든 이상 허용
- 동시성 최고
- 안전 최저
→ 거의 안 씀
READ COMMITTED:
- Dirty Read 차단
- 다른 이상 허용
→ Oracle/PostgreSQL/SQL Server 기본
→ 실용적 균형
REPEATABLE READ:
- Dirty + Non-repeatable 차단
- Phantom 허용 (표준)
- MySQL InnoDB 는 Phantom 도 차단
→ MySQL 기본
SERIALIZABLE:
- 모든 이상 차단
- 직렬 실행처럼
- 가장 안전, 가장 느림
→ 특수 케이스만
-- ILIC 격리 수준 활용
-- 기본 (MySQL REPEATABLE READ)
-- 대부분 OK
-- 특수: 빠른 분석 (Dirty 허용)
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT COUNT(*) FROM shipments WHERE created_at > NOW() - INTERVAL 1 HOUR;
-- 약간 부정확해도 빠름
-- 특수: 회계 정확성 (가장 엄격)
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 결제, 정산 등
BEGIN;
-- 매우 엄격
COMMIT;
-- 보통은 기본 REPEATABLE READ
격리 수준별 차단은?
답:
1. 표:
RC:
RR:
SER:
높은 격리 부작용:
SERIALIZABLE:
- 락 많음
- 동시성 ↓
- 응답 시간 ↑
- 데드락 ↑
- 처리량 ↓
트레이드오프:
격리 ↑:
- 안전 ↑
- 동시성 ↓
- 성능 ↓
격리 ↓:
- 안전 ↓
- 동시성 ↑
- 성능 ↑
→ 균형 필요
실용적 선택:
대부분: READ COMMITTED 또는 REPEATABLE READ
- 균형
- DB 기본값 사용
특수만: SERIALIZABLE
- 금융, 결제
- 정합성 최우선
→ 기본값이 보통 최선
락의 영향:
격리 높음:
- 락 더 많이
- 대기 시간 ↑
- 데드락 ↑ (Phase 4.4)
→ 락 시간 짧게
ILIC 격리 수준 선택
ILIC 시스템:
- 멀티 사용자 (포워더/화주/관리자)
- 동시 작업 많음
- 적절한 격리 필요
선택: MySQL InnoDB 기본 (REPEATABLE READ)
- Dirty/Non-repeatable 차단
- Phantom 도 차단 (InnoDB 갭 락)
- 적절한 동시성
- 데드락 적당히 (관리 가능)
특수 케이스:
- 정산 (회계): SERIALIZABLE (선택)
- 통계 (대량 분석): READ COMMITTED (성능)
→ 기본 + 특수만 변경
격리 수준이 너무 높으면 어떤 문제?
답:
1. 부작용:
트레이드오프:
실용:
락 영향:
// Spring 격리 수준 설정
@Transactional(isolation = Isolation.READ_COMMITTED)
public void readOnly() { }
@Transactional(isolation = Isolation.SERIALIZABLE)
public void critical() { }
// 기본: Isolation.DEFAULT (DB 기본)
// Spring Isolation enum
public enum Isolation {
DEFAULT,
READ_UNCOMMITTED,
READ_COMMITTED,
REPEATABLE_READ,
SERIALIZABLE
}
메서드별 선택:
일반: DEFAULT (DB 기본)
분석: READ_COMMITTED (성능)
정산: SERIALIZABLE (안전)
→ 비즈니스 맥락
주의:
격리 수준:
- 트랜잭션 시작 시 결정
- 중간 변경 X
Spring 적용:
- 메서드 진입 시
- AOP
// ILIC Spring 격리 (@Transactional)
@Service
public class ShipmentService {
// 일반 (DB 기본 = MySQL RR)
@Transactional
public void create(Shipment s) {
shipmentDao.add(s);
}
// 분석 (성능, Phantom 허용)
@Transactional(isolation = Isolation.READ_COMMITTED, readOnly = true)
public List<Stat> getMonthlyStats() {
return statsDao.findMonthly();
// RR 보다 빠름 (락 적게)
}
// 정산 (안전 최우선)
@Transactional(isolation = Isolation.SERIALIZABLE)
public void settleMonthlyFreight() {
// 모든 이상 차단
// 정확한 정산
}
ShipmentDao shipmentDao;
StatsDao statsDao;
}
class Shipment {}
class Stat {}
class ShipmentDao { void add(Shipment s) {} }
class StatsDao { java.util.List<Stat> findMonthly() { return null; } }
| Q | 핵심 답변 |
|---|---|
| Isolation? | 트랜잭션 독립 |
| 1만원 시나리오? | 격리 없으면 -2천 |
| 4단계? | RU/RC/RR/SER |
| Dirty Read? | 커밋 전 읽기 |
| Non-repeatable? | 같은 행 두 번 다름 |
| Phantom? | 새 행 등장 |
| MySQL 기본? | REPEATABLE READ |
| 높으면? | 동시성 ↓ |
| Spring 설정? | @Transactional isolation |
| InnoDB 특별? | RR 에서 Phantom 차단 |
답:
답:
답:
답:
답:
1. Isolation (격리성)
2. 격리 수준 4단계와 이상 현상
3. 트레이드오프
이번 Unit에서 Isolation 을 봤다면, 다음은 Durability (★ 깊이 파기).
💎 Phase 6 — 트랜잭션과 ACID
✅ Unit 6.1 트랜잭션이란
✅ Unit 6.2 Atomicity ★깊이
✅ Unit 6.3 Consistency ★깊이
✅ Unit 6.4 Isolation ★깊이 ← 여기
⏭ Unit 6.5 Durability ★깊이
⏭ Unit 6.6 Commit 이전 동시성 ★깊이
🧪 Part A (9 Unit) ✅
💾 Part B — DB 접근의 진화
✅ Phase 3 — JDBC (3)
✅ Phase 4 — Connection Pool (4)
✅ Phase 5 — DataSource (4)
💎 Phase 6 — ACID (4/6)
총: 24/28 Unit
★ 깊이 파기 — Isolation 완료