Skip to content

트랜잭션 격리수준

동시에 실행되는 트랜잭션이 서로의 변경을 어디까지 볼 수 있는가를 정하는 규칙. 격리를 강하게 할수록 이상 현상은 줄고 동시성은 떨어진다.

왜 필요한가

트랜잭션을 완전히 한 줄로 세워 실행하면 안전하지만 느리다. 반대로 아무 제약 없이 섞어 돌리면 커밋되지 않은 값을 읽거나, 같은 행을 두 번 읽었는데 값이 달라지는 일이 생긴다. 격리수준은 이 둘 사이에서 어떤 이상 현상까지 허용할지를 고르는 다이얼이다. SQL 표준(SQL-92)이 4단계를 정의하고, 엔진마다 실제 동작은 조금씩 다르다.

4단계와 이상 현상

격리수준Dirty ReadNon-Repeatable ReadPhantom Read
READ UNCOMMITTED발생발생발생
READ COMMITTED방지발생발생
REPEATABLE READ (InnoDB 기본)방지방지표준은 허용, InnoDB는 대부분 방지*
SERIALIZABLE방지방지방지

* 표준 정의상 REPEATABLE READ는 Phantom Read를 허용하지만, InnoDB는 Gap Lock(Next-Key Lock)으로 범위 안 삽입을 막아 대부분 방지한다. 면접에서 자주 파고드는 지점.

세 이상 현상은 이렇게 구분한다.

  • Dirty Read: 다른 트랜잭션이 아직 커밋하지 않은 값을 읽는다. 그 트랜잭션이 롤백하면 존재한 적 없는 값을 읽은 셈이 된다.
  • Non-Repeatable Read: 같은 행을 두 번 읽었는데 사이에 다른 트랜잭션이 커밋해 값이 달라진다.
  • Phantom Read: 같은 조건으로 두 번 조회했는데 사이에 다른 트랜잭션이 삽입·삭제해 행의 개수가 달라진다.

Non-Repeatable Read 한 장면

다이어그램 로딩…

REPEATABLE READ였다면 T1은 첫 SELECT 시점의 스냅샷을 계속 보므로 두 번째도 100이 나온다.

MySQL/InnoDB 기준 동작

  • 기본값은 REPEATABLE READ. 트랜잭션의 첫 일관된 읽기(consistent read) 시점에 스냅샷을 만들고, 트랜잭션이 끝날 때까지 같은 스냅샷을 본다. 스냅샷은 Undo Log의 이전 버전으로 재구성한다(MVCC).
  • READ COMMITTED문장마다 새 스냅샷을 만든다. 그래서 같은 트랜잭션 안에서도 문장 사이에 커밋된 변경이 보인다. 바이너리 로그는 ROW 포맷이어야 한다.
  • SELECT ... FOR UPDATE, FOR SHARE, UPDATE, DELETE 같은 잠금 읽기는 스냅샷이 아니라 최신 커밋 값을 읽는다. "REPEATABLE READ인데 왜 최신 값이 보이지?"의 답이 대부분 여기 있다.
  • SERIALIZABLE은 모든 일반 SELECTFOR SHARE처럼 바꿔 읽기에도 공유 잠금을 건다. 동시성이 크게 떨어져 실무에서는 거의 쓰지 않는다.
  • READ UNCOMMITTED는 커밋 전 값을 그대로 읽는다. 통계용 근사치 조회 외에는 쓸 이유가 없다.
sql
-- 현재 세션 / 서버 기본값 확인 (MySQL 8: tx_isolation 아님)
SELECT @@SESSION.transaction_isolation, @@GLOBAL.transaction_isolation;

-- 다음에 시작하는 한 트랜잭션에만 적용 (SESSION/GLOBAL 생략)
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
-- ...
COMMIT;

-- 이 커넥션 전체에 적용
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

다른 엔진과 차이

엔진기본값메모
MySQL InnoDBREPEATABLE READGap Lock으로 Phantom 대부분 방지
PostgreSQLREAD COMMITTEDREPEATABLE READ는 스냅샷 격리. 갱신 충돌 시 에러로 실패시킴
OracleREAD COMMITTEDREAD UNCOMMITTED·REPEATABLE READ 없음. SERIALIZABLE은 스냅샷 격리
SQL ServerREAD COMMITTED기본은 잠금 기반. 스냅샷 격리는 옵션

"기본 격리수준이 뭐냐"는 질문에 MySQL만 REPEATABLE READ라는 점을 짚어 주면 좋다.

흔한 오해

오해 1 — "REPEATABLE READ면 Phantom Read는 무조건 발생한다"

표준 정의는 그렇지만 InnoDB는 Next-Key Lock으로 대부분 막는다. 반대로 "InnoDB에서는 절대 안 난다"도 틀렸다. 스냅샷 읽기만 하던 트랜잭션이 중간에 UPDATE를 하면, 그 문장은 최신 커밋 값을 대상으로 하므로 다른 트랜잭션이 삽입한 행이 갱신 대상에 들어오고, 이후 스냅샷에 그 행이 보이게 된다.

오해 2 — "격리수준을 올리면 갱신 손실(Lost Update)이 해결된다"

재고 차감처럼 "읽고 → 판단하고 → 쓰는" 패턴은 REPEATABLE READ에서도 두 트랜잭션이 같은 스냅샷을 보고 둘 다 차감할 수 있다. 격리수준은 읽기 일관성 도구다. 갱신 충돌은 SELECT ... FOR UPDATE, 조건부 UPDATE ... WHERE stock > 0, 낙관적 버전 컬럼으로 푼다.

기억법

격리수준 4개는 "이상 현상 3개를 위에서부터 하나씩 끈다"로 외운다. Dirty → Non-Repeatable → Phantom.

실무 연결 포인트

  • 대부분의 서비스는 InnoDB 기본값(REPEATABLE READ)을 그대로 쓴다. 바꿀 때는 이유가 있어야 한다. 긴 배치 트랜잭션에서 오래된 스냅샷을 유지하느라 Undo가 커지는 문제는 READ COMMITTED로 완화할 수 있다.
  • 격리수준을 낮추는 대신 트랜잭션을 짧게 만드는 편이 먼저다. 스냅샷과 잠금 보유 시간이 곧 비용이다.
  • 프레임워크의 @Transactional(isolation = ...) 은 결국 SET TRANSACTION ISOLATION LEVEL을 커넥션에 실행하는 것이다. 커넥션 풀을 쓰면 반납 시 원복되는지 확인한다.

면접에서 이렇게 묻는다

"격리수준 설명해 주세요" → "MySQL 기본값은요?" → "그럼 Phantom Read는 InnoDB에서 발생하나요?" → "그러면 재고 차감은 어떻게 막나요?" 순으로 파고든다. 표 하나와 예외 두 개를 말할 수 있으면 통과.

바로 확인

바로 확인 · 3문항
Q1난이도

MySQL InnoDB의 기본 트랜잭션 격리수준은?

Q2난이도

InnoDB REPEATABLE READ에서 Phantom Read를 막는 데 쓰이는 잠금은?

Q3난이도 { } 괄호 채우기

현재 세션의 격리수준을 조회하는 MySQL 8 구문

SELECT @@.transaction_isolation;
빈칸 1 채우기

관련 문서

  • MVCC (예정) — 스냅샷을 만드는 방법
  • 잠금 · Gap Lock (예정) — Phantom을 막는 장치