트랜잭션 격리 수준은 왜 필요할까: dirty read, non-repeatable read, phantom read 쉽게 보기

트랜잭션 격리 수준은 왜 필요할까: dirty read, non-repeatable read, phantom read 쉽게 보기
격리 수준은 동시에 실행되는 트랜잭션 사이에서 어디까지 서로를 보게 할지 정하는 약속입니다.

dirty read 같은 이상 현상을 이해하면 트랜잭션 격리 수준이 왜 필요한지 훨씬 선명해집니다. 여러 사용자가 동시에 같은 데이터를 읽고 쓸 때 서로를 어디까지 보게 할지 정하는 규칙이기 때문입니다.

핵심은 정합성과 동시성 사이의 균형입니다. 이 글은 PostgreSQL과 MySQL 공식 문서를 참고해 dirty read, non-repeatable read, phantom read를 단계적으로 설명합니다.

트랜잭션 격리 수준 이상 현상 요약 카드
dirty read, non-repeatable read, phantom read는 모두 동시에 읽고 쓰는 과정에서 생기는 관찰 문제입니다.

트랜잭션 격리 수준은 무엇을 막기 위한 걸까

트랜잭션은 여러 데이터 변경을 하나의 작업 단위로 묶습니다. 문제는 여러 트랜잭션이 동시에 실행될 때 생깁니다. 한쪽은 아직 확정하지 않은 값을 쓰고, 다른 쪽은 그 값을 읽을 수 있기 때문입니다.

격리 수준은 이때 읽기를 얼마나 엄격하게 보호할지 정합니다. 엄격할수록 이해하기 쉬운 결과를 얻기 쉽지만, 동시 처리 성능과 대기 비용이 커질 수 있습니다.


dirty read는 아직 확정되지 않은 값을 읽는 문제

dirty read는 다른 트랜잭션이 아직 commit하지 않은 변경을 읽는 상황입니다. 그 변경이 나중에 rollback되면 나는 존재하지 않았던 값을 기준으로 판단한 셈이 됩니다.

-- Transaction A
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 아직 COMMIT 전

-- Transaction B
SELECT balance FROM accounts WHERE id = 1;

대부분의 현대 데이터베이스 기본 설정은 dirty read를 허용하지 않는 쪽입니다. 하지만 개념을 이해하면 왜 최소한의 격리가 필요한지 알 수 있습니다.


non-repeatable read는 같은 행의 값이 바뀌는 문제

non-repeatable read는 한 트랜잭션 안에서 같은 행을 두 번 읽었는데 중간에 다른 트랜잭션이 commit해서 값이 달라지는 현상입니다.

  • 첫 번째 조회: 상품 가격이 10,000원
  • 다른 트랜잭션이 가격을 12,000원으로 수정 후 commit
  • 두 번째 조회: 같은 상품 가격이 12,000원

조회 흐름 전체에서 같은 값을 기대하는 업무라면 문제가 될 수 있습니다. 반대로 최신 commit 값을 보는 것이 더 자연스러운 업무도 있습니다.


phantom read는 조건에 맞는 행 집합이 달라지는 문제

phantom read는 같은 조건으로 다시 조회했는데 행의 개수가 달라지는 상황입니다. 기존 행의 값 변화가 아니라 새 행의 삽입이나 삭제 때문에 결과 집합이 달라지는 문제입니다.

SELECT * FROM orders WHERE status = 'READY';
-- 중간에 다른 트랜잭션이 READY 주문을 추가하고 COMMIT
SELECT * FROM orders WHERE status = 'READY';

통계, 정산, 재고 배정처럼 같은 조건의 집합이 유지되어야 하는 작업에서는 phantom read를 특히 조심해야 합니다.


실무에서 격리 수준을 고르는 기준

  1. 기본 격리 수준을 먼저 확인한다. DB마다 기본값과 동작 세부가 다를 수 있다
  2. 돈, 재고, 정산처럼 잘못된 중복 처리 비용이 큰 작업은 더 엄격하게 본다
  3. 단순 조회나 로그성 데이터는 과한 격리보다 처리량이 중요할 수 있다
  4. 격리 수준만 믿지 말고 unique constraint, optimistic lock, retry 전략을 함께 본다
  5. 운영 DB에서는 긴 트랜잭션이 lock 대기와 장애로 이어질 수 있음을 고려한다

정리

dirty read를 이해할 때는 명령어나 패턴 이름만 외우기보다 그 도구가 해결하려는 경계를 먼저 보는 편이 좋습니다. 오늘 글의 기준은 화면과 데이터, 실행과 import, 정상 흐름과 오류 흐름, 정합성과 동시성, 개인 브랜치와 공유 브랜치를 나눠 보는 것입니다.

함께 보면 좋은 내부 글은 SQL 인덱스는 왜 빠를까, Spring Boot 예외 처리는 어디서 모아야 할까, JWT access token과 refresh token은 왜 나눌까입니다. 외부 기준은 PostgreSQL Docs – Transaction Isolation, MySQL Docs – InnoDB Transaction Isolation Levels, SQL standard terms via PostgreSQL Docs를 확인했습니다.

함께보면 좋은 글