Skip to content
난이도 면접 빈출문제은행 7

트랜잭션 격리수준 2 ​

트랜잭션 격리수준에서 본 4단계를 MySQL의 InnoDB가 실제로 어떻게 구현하는지 이어서 다룬다. 동시에 실행되는 트랜잭션(transaction)이 서로의 변경을 어디까지 볼지 정하는 격리 수준(isolation level)은 InnoDB에서 두 가지 읽기로 나뉘어 동작한다. 하나는 한 시점의 스냅샷(snapshot)을 읽는 일관된 읽기(consistent read)이고, 다른 하나는 가장 최신 버전을 읽으며 잠금을 거는 현재 읽기(current read)다. 다른 엔진의 기본값, 자주 틀리는 오해, 실무에서 조심할 점도 정리한다.

MySQL/InnoDB 기준 동작 ​

  • 기본값은 REPEATABLE READ다. 트랜잭션 안에서 처음으로 일관된 읽기를 하는 시점에 스냅샷을 만들고, 트랜잭션이 끝날 때까지 같은 스냅샷을 본다. 스냅샷은 언두 로그(undo log)에 남은 이전 버전으로 재구성한다. 이렇게 행의 여러 버전을 두고 골라 읽는 방식을 MVCC(Multi-Version Concurrency Control, 다중 버전 동시성 제어)라 한다.
  • READ COMMITTED는 문장마다 새 스냅샷을 만든다. 그래서 같은 트랜잭션 안에서도 문장 사이에 커밋(commit)된 변경이 보인다. 이 수준에서는 바이너리 로그(binary log)를 행 기반으로만 쓸 수 있다. binlog_format=MIXED로 두면 서버가 자동으로 행 기반으로 기록한다(포맷 차이는 바이너리 로그 포맷과 동기화 방식).
  • SELECT ... FOR UPDATE, FOR SHARE, UPDATE, DELETE는 읽기 뷰(read view)가 아니라 현재 버전을 대상으로 하는 현재 읽기다. 필요한 레코드에 잠금을 걸며 자기 트랜잭션의 변경도 본다.
  • SERIALIZABLE은 자동 커밋(autocommit)이 꺼진 상태의 일반 SELECT를 FOR SHARE처럼 잠금 읽기(locking read)로 바꾼다. 동시성이 크게 떨어져 적용 범위를 신중히 잡는다.
  • 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 READ갭 락(gap lock)으로 팬텀 대부분 방지
PostgreSQLREAD COMMITTEDREPEATABLE READ는 스냅샷 격리(snapshot isolation). 갱신 충돌 시 에러로 실패시킴
OracleREAD COMMITTEDREAD UNCOMMITTED·REPEATABLE READ 없음. SERIALIZABLE은 스냅샷 격리
SQL ServerREAD COMMITTED기본은 잠금 기반. 스냅샷 격리는 옵션

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

흔한 오해 ​

오해 1 — "REPEATABLE READ면 팬텀 리드(phantom read)는 무조건 발생한다"

InnoDB의 일반 일관된 읽기는 같은 읽기 뷰를 유지하고, 잠금 읽기는 넥스트키 락(next-key lock)으로 스캔 범위를 보호한다. 반대로 "어떤 문장을 섞어도 절대 없다"도 틀렸다. 일관된 읽기와 현재 읽기를 한 트랜잭션에서 섞으면 서로 다른 시점 기준의 결과를 관찰할 수 있다.

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

갱신 손실은 두 트랜잭션이 같은 행을 고칠 때 한쪽의 변경이 다른 쪽에 덮여 사라지는 현상이다. 재고를 먼저 일반 SELECT로 읽고, 애플리케이션에서 계산한 절대값을 다시 쓰면 이런 일이 생길 수 있다. 두 트랜잭션이 같은 값을 읽고 나중 쓰기가 앞선 결과를 덮기 때문이다. 반면 UPDATE ... SET stock = stock - 1 WHERE stock > 0은 행 잠금을 건 채 최신 값을 기준으로 조건 확인과 계산을 한 번에(원자적으로) 처리한다. 갱신 충돌은 잠금 읽기, 조건부 UPDATE, 낙관적 잠금(optimistic locking)처럼 쓰기 방식으로 해결한다.

실무 연결 포인트 ​

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

면접에서 이렇게 묻는다

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

관련 문서