MySQL에서 트랜잭션 격리 수준을 이해할 때 가장 까다로운 비교가 바로 REPEATABLE READ와 SERIALIZABLE이다.
두 수준 모두 데이터 정합성을 높이기 위한 방식이지만, 동작 방식과 성능에 큰 차이가 있다.
특히 Phantom Read(팬텀 리드) 개념이 핵심인데, 이 개념을 얼마나 잘 이해하느냐가 실무에서도 중요한 포인트다.

🔎 REPEATABLE READ란?
- 트랜잭션이 시작된 시점의 스냅샷을 기준으로 SELECT를 유지
- 같은 쿼리를 여러 번 실행해도 항상 같은 결과를 반환함
- Non-repeatable Read는 방지되지만, 기본적으로는 Phantom Read가 발생할 수 있음
- 그러나 MySQL InnoDB는 Gap Lock을 사용해 Phantom Read까지 막는다
✅ 특징 요약
- SELECT 결과는 고정됨 (스냅샷 기반)
- INSERT나 DELETE로 인해 레코드 개수가 바뀌는 현상이 제한적으로 발생 가능
- MySQL은 내부적으로 팬텀 리드를 막아주기 때문에 일반적으로는 안정적이다
🧪 예시: 팬텀 리드 체감하기
-- 세션 A
START TRANSACTION;
SELECT * FROM orders WHERE user_id = 10;
-- 세션 B
INSERT INTO orders (user_id, item) VALUES (10, 'Keyboard');
COMMIT;
-- 세션 A
SELECT * FROM orders WHERE user_id = 10;
-- REPEATABLE READ에서는 새로운 주문이 안 보일 수 있음
MySQL에서는 Gap Lock으로 인해 세션 B의 INSERT 자체가 락 대기 상태가 되거나,
트랜잭션 A가 끝날 때까지 삽입이 지연된다.
즉, 팬텀 리드를 방지하기 위한 락이 동작하고 있는 셈이다.
🔎 SERIALIZABLE이란?
- 가장 엄격한 격리 수준
- 모든 SELECT 문조차도 공유 락(Shared Lock)을 걸어야 함
- 트랜잭션이 정말 직렬로 수행되는 것처럼 보이도록 보장
- Phantom Read, Dirty Read, Non-repeatable Read 전부 방지
✅ 특징 요약
- SELECT도 락을 걸기 때문에 성능이 크게 저하됨
- 동시에 여러 트랜잭션이 접근할 경우 Lock Wait Timeout이 자주 발생함
- 데이터 정합성이 최우선인 상황에서만 제한적으로 사용
🧪 예시: SERIALIZABLE에서 INSERT 차단
-- 세션 A
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
SELECT * FROM orders WHERE user_id = 10;
-- 세션 B
INSERT INTO orders (user_id, item) VALUES (10, 'Mouse');
-- → 세션 A가 끝날 때까지 락 대기 상태
SERIALIZABLE은 SELECT 쿼리조차도 해당 범위에 락을 건다.
그 결과, 세션 B는 단순한 INSERT도 대기하거나 실패하게 된다.
✅ 두 격리 수준 비교 요약
| 항목 | REPEATABLE READ | SERIALIZABLE |
| Dirty Read | 방지됨 | 방지됨 |
| Non-repeatable Read | 방지됨 | 방지됨 |
| Phantom Read | (MySQL InnoDB에서는 방지됨) | 무조건 방지됨 |
| SELECT 시 Lock | 없음 (Gap Lock은 있음) | Shared Lock 발생 |
| 성능 영향 | 보통 | 높음 (락 경합 심함) |
| 실무 추천 사용처 | 일반적인 OLTP 시스템 | 배치 처리, 통계 추출 등 제한적 사용 |
💡 실무에서 어떻게 선택할까?
REPEATABLE READ는 MySQL InnoDB의 기본 설정이며, 대부분의 상황에서 무리 없이 사용 가능하다.
팬텀 리드가 우려되는 상황에서도 MySQL은 Gap Lock으로 이를 방지하므로 별도 조정 없이도 안전하다.
SERIALIZABLE은 동시성 성능에 큰 영향을 주기 때문에 실제 운영 환경에서는 잘 사용하지 않는다.
정확한 결과가 가장 중요할 때, 예를 들어 통계 계산, 회계 정산, 보고서 생성 등의 배치 작업에만 사용하도록 한다.
다중 트랜잭션이 동시에 수행될 가능성이 높은 웹 서비스 환경에서는 SERIALIZABLE 사용은 지양해야 한다.
락 경합이 심해지고 트랜잭션 충돌이 빈번해지며, 결국 성능 병목이 생긴다.
📘 공식 문서 참고
https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html
'DB' 카테고리의 다른 글
| [MySQL] (보안설정2️⃣) 데이터 at rest 암호화 설정법: 테이블·디스크 암호화 실무 가이드 (0) | 2025.07.16 |
|---|---|
| [MySQL] (보안설정1️⃣) 데이터 암호화가 필요한 이유와 기본 개념 정리 (1) | 2025.07.16 |
| [MySQL] (트랜잭션 격리수준5️⃣) 실무에서 격리 수준 설정은 이렇게 한다 (1) | 2025.07.16 |
| [MySQL] (트랜잭션 격리수준4️⃣) 격리 수준별 현상 실습: Dirty ~ Phantom Read (0) | 2025.07.16 |
| [MySQL] (트랜잭션 격리수준2️⃣) READ UNCOMMITTED vs READ COMMITTED (0) | 2025.07.15 |
| [MySQL] (트랜잭션 격리수준1️⃣) 격리 수준이란? 4단계 개념 정복 (0) | 2025.07.15 |
| [MySQL] (환경설정6️⃣) 자주 실수하는 my.cnf 설정 오류 사례 (0) | 2025.07.15 |
| [MySQL] (환경설정5️⃣) my.cnf 실전 튜닝: 운영환경별 추천값 정리 (0) | 2025.07.15 |