Phantom Read란? 트랜잭션 격리 수준에서 생기는 이상 현상

Phantom Read란 무엇인가

Phantom Read(팬텀 리드)는 데이터베이스 트랜잭션 격리 수준에서 발생할 수 있는 이상 현상 중 하나로, 하나의 트랜잭션 내에서 동일한 조건으로 두 번 조회했을 때 조회 결과의 ‘행 개수’가 달라지는 현상을 말한다. 이름 그대로 마치 유령처럼 없던 데이터가 나타나거나, 있던 데이터가 사라지는 것처럼 보인다고 해서 붙여진 이름이다.

흔히 헷갈리는 Non-Repeatable Read와 비교하면 이해가 쉽다. Non-Repeatable Read는 같은 행을 다시 읽었을 때 값이 바뀌는 현상이고, Phantom Read는 조회 조건에 맞는 행의 ‘집합’ 자체가 바뀌는 현상이다. 즉 수정(UPDATE)이 아니라 삽입(INSERT)이나 삭제(DELETE)로 인해 발생한다는 차이가 있다.

어떻게 발생하는가: 예시로 보기

온라인 쇼핑몰에서 ‘가격이 10000원 이상인 상품’ 개수를 세는 트랜잭션 A가 있다고 가정하자.

  • 트랜잭션 A: SELECT COUNT(*) FROM products WHERE price >= 10000; → 결과 5건
  • 이때 트랜잭션 B가 가격 15000원짜리 상품을 새로 INSERT하고 커밋
  • 트랜잭션 A: 동일한 쿼리를 다시 실행 → 결과 6건

같은 트랜잭션 안에서 똑같은 조건으로 조회했는데 결과 건수가 달라졌다. 이것이 바로 Phantom Read다. 도서관에서 ‘역사책 코너에 몇 권이 있나’ 세고 있는데, 옆에서 사서가 새 책을 몰래 꽂아 넣어서 다시 셌더니 숫자가 늘어난 상황과 비슷하다고 생각하면 이해하기 쉽다.

이 현상은 SQL 표준에서 정의한 4단계 격리 수준(Read Uncommitted, Read Committed, Repeatable Read, Serializable) 중 Repeatable Read 수준까지도 이론적으로는 발생할 수 있다고 알려져 있다. 다만 MySQL의 InnoDB는 Repeatable Read에서도 Next-Key Lock이라는 기법을 이용해 Phantom Read를 상당 부분 방지한다.

실무에서 왜 신경 써야 하는가

Phantom Read는 정산, 재고 관리, 좌석 예약처럼 ‘조건에 맞는 전체 행 집합’을 기준으로 로직을 처리하는 배치 작업에서 특히 문제가 된다. 예를 들어 재고가 특정 수량 이하인 상품을 모두 조회해서 일괄 할인 처리를 하는 도중, 다른 트랜잭션이 새 상품을 추가하면 의도치 않게 방금 추가된 상품까지 할인 대상에 포함되거나 누락될 수 있다.

이를 막기 위해서는 격리 수준을 Serializable로 올리거나, 애플리케이션 레벨에서 명시적 락인 SELECT ... FOR UPDATE를 사용하는 방법이 있다. 다만 격리 수준을 높일수록 동시성 성능이 떨어지므로, 서비스 특성에 맞게 ‘정합성’과 ‘처리량’ 사이의 균형을 찾는 것이 중요하다. 결국 Phantom Read를 이해한다는 것은 트랜잭션 격리 수준이 트레이드오프의 산물이라는 사실을 이해하는 것과 같다.

댓글 남기기