DB

트랜잭션과 격리 수준

트랜잭션 두 개가 같은 데이터를 동시에 다루면, 한쪽이 다른 쪽의 중간 상태를 보게 될 수 있습니다. 격리 수준은 "다른 트랜잭션의 변경을 어디까지 보지 않을 것인가"를 정하는 설정입니다. 수준이 높을수록 안전하지만 기다리는 시간이 늘어납니다.

두 트랜잭션의 실행 순서와 격리 수준에 따라 읽는 값이 달라지는 과정을 보여 주는 시뮬레이터

1. 상황과 격리 수준을 고르세요

상황
격리 수준

2. 시간 순서대로 실행하세요

데이터베이스

왼쪽 칸이 트랜잭션 T1, 오른쪽 칸이 T2입니다. 같은 상황을 격리 수준만 바꿔 다시 실행하면 T1이 읽는 값이 달라집니다. 아래 표에서 지금 보고 있는 칸이 강조됩니다.

A 원자성전부 아니면 전무
C 일관성규칙을 항상 지킴
I 격리성서로 간섭하지 않음
D 지속성커밋하면 남음

세 가지 이상 현상

Dirty Read는 다른 트랜잭션이 아직 커밋하지 않은 값을 읽는 것입니다. 그 트랜잭션이 롤백하면 존재한 적 없는 값을 읽은 셈이 됩니다. Non-repeatable Read는 한 트랜잭션 안에서 같은 행을 두 번 읽었는데, 그사이 다른 트랜잭션이 고치고 커밋해서 값이 달라지는 것입니다. Phantom Read는 같은 조건으로 두 번 조회했는데, 그사이 다른 트랜잭션이 조건에 맞는 행을 새로 넣어 행 수가 달라지는 것입니다.

DB마다 기본값과 구현이 다릅니다

PostgreSQL과 Oracle의 기본값은 READ COMMITTED, MySQL InnoDB의 기본값은 REPEATABLE READ입니다. 요즘 DB는 대부분 MVCC 방식으로, 데이터의 이전 버전을 보관해 두고 트랜잭션마다 알맞은 버전을 보여 줍니다. 그래서 읽는 쪽이 쓰는 쪽을 기다리지 않습니다. MySQL InnoDB의 REPEATABLE READ는 스냅숏 덕분에 표준과 달리 일반 SELECT에서 Phantom Read가 나타나지 않습니다. 이 페이지의 SERIALIZABLE은 잠금으로 구현하는 방식을 보여 줍니다.

면접에서 자주 묻는 것

"격리 수준을 높이면 무엇을 잃는가"에는 동시 처리량이라고 답합니다. 잠금 방식이라면 기다리는 트랜잭션이 늘고 교착 상태 가능성도 커집니다. 재고 차감처럼 읽고 나서 고치는 작업은 격리 수준을 올리는 대신 SELECT ... FOR UPDATE로 필요한 행만 잠그는 경우가 많습니다.