우선 정말 대략적인 설명으론 InnoDB는 MySQL의 대표적인 스토리지 엔진입니다. 쉽게 말해, MySQL이 데이터를 어떻게 저장하고 관리할지를 결정하는 내부 시스템입니다. MySQL은 하나의 데이터베이스 시스템이지만, 내부에서 다양한 스토리지 엔진을 선택할 수 있고, 그 중에서도 InnoDB는 기본(default) 스토리지 엔진이자 가장 널리 사용되는 엔진입니다.
그렇다면 Phantom Read란 무엇인가요?
Phantom Read는 트랜잭션이 동일한 조건의 쿼리를 반복 실행할 때, 나중에 실행된 쿼리에서 처음에는 존재하지 않았던 새로운 행이 나타나는 현상을 말합니다. 이는 주로 읽기 일관성(Read Consistency) 을 유지하는 과정에서 발생할 수 있는 문제로, 데이터의 삽입이나 삭제가 다른 트랜잭션에 의해 이루어질 때 발생합니다.

팬텀 리드를 막기 위한 갭 락과 넥스트키 락
다시 정리하자면 팬텀 리드는 트랜잭션이 실행 중인 동안, 동일한 조건으로 다시 조회했을 때 처음에는 없었던 새로운 레코드가 등장하는 현상을 의미합니다.
특히 범위 기반의 검색(예: BETWEEN, <, >)에서 자주 발생할 수 있으며, 데이터 일관성을 깨뜨릴 수 있는 치명적인 문제입니다. 이를 방지하기 위해 InnoDB 저장 엔진은 갭 락(Gap Lock)과 넥스트키 락(Next-Key Lock)이라는 잠금 메커니즘을 사용합니다.
갭 락(Gap Lock)
갭 락은 특정 인덱스 값 사이의 공간을 잠그는 락입니다. 기존 레코드 간의 간격을 보호하여 새로운 레코드의 삽입을 방지합니다. 갭 락은 범위 내에 특정 레코드가 존재하지 않을 때 적용됩니다. 즉 해당 범위에 실제 레코드가 없어도, 그 사이에 새로운 레코드가 삽입되지 못하도록 막는 락입니다.

트랜잭션이 특정 범위 내에서 데이터의 삽입을 막아 팬텀 읽기(Phantom Read) 현상을 방지합니다. 예를 들어, 인덱스 값 10과 20 사이의 갭을 잠그면 이 범위 내에 새로운 레코드 15를 추가할 수 없습니다.
-- id 1, 3, 5가 저장된 orders 테이블
-- 트랜잭션 A 시작
START TRANSACTION;
-- 트랜잭션 A 1-3과 3-5 사이의 갭과 3 레코드 락 설정(넥스트키 락)
SELECT * FROM orders WHERE orders_id BETWEEN 2 AND 4 FOR UPDATE;
-- 트랜잭션 B 시작
START TRANSACTION;
-- 트랜잭션 B가 id 4에 데이터 삽입 시도 시, 갭락으로 인해 삽입이 차단되어 대기
INSERT INTO orders (orders_id, orders_amount) VALUES (4, 200);
넥스트키 락 (Next-Key Lock)

넥스트키 락은 레코드 락과 갭락을 결합한 형태로, 특정 인덱스 레코드와 그 주변의 갭을 동시에 잠그는 락입니다. 이를 통해 레코드 자체의 변경과 함께 그 주변 공간의 변경도 동시에 제어할 수 있습니다.
넥스트키 락은 특정 레코드와 그 주변 공간을 잠그기 때문에, 다른 트랜잭션이 새로운 레코드를 삽입하여 팬텀 리드를 발생시키는 것을 방지합니다.
| orders_id | orders_amount |
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
-- 트랜잭션 A 시작
START TRANSACTION;
-- 트랜잭션 A amount = 200인 orders_id = 2 레코드에 대한 레코드 락과 1-2, 2-3에 대한 갭락을 동시에 잠금으로써 넥스트키 락을 설정
SELECT * FROM orders WHERE orders_amount = 200 FOR UPDATE;
-- 트랜잭션 B 시작
START TRANSACTION;
-- 트랜잭션 B orders_id = 4, orders_amount = 200인 레코드 삽입 시도 시, 넥스트키 락으로 인해 차단되어 대기
INSERT INTO orders (orders_id, order_amount) VALUES (4, 200);
갭락과 넥스트키 락을 통한 팬텀 리드 방지 메커니즘
트랜잭션 A가 특정 범위의 데이터를 조회할 때, 해당 범위에 대해 갭락 또는 넥스트키 락을 설정합니다. 락이 설정된 범위 내에서는 트랜잭션 B가 새로운 레코드를 삽입하거나 기존 레코드를 수정하는 것이 차단됩니다. 따라서, 트랜잭션 A가 다시 동일한 조건으로 조회를 수행하더라도, 트랜잭션 B에 의해 새로운 데이터가 삽입되지 않아 팬텀 리드가 발생하지 않습니다.
마무리
이처럼 InnoDB는 단순히 데이터를 저장하는 역할을 넘어서, 트랜잭션의 격리성과 데이터의 일관성을 보장하기 위해 내부적으로 다양한 잠금 메커니즘을 동작시킵니다. 그중에서도 팬텀 리드를 방지하는 과정은 MVCC만으로는 충분하지 않으며, 개발자가 SELECT ... FOR UPDATE와 같은 비관적 락 쿼리를 사용할 때 InnoDB가 자동으로 갭 락이나 넥스트키 락을 적용함으로써 이루어집니다.
결국 팬텀 리드를 완전히 차단하고 싶다면, 단순한 SELECT 쿼리만으로는 부족하며, 트랜잭션 격리 수준(REPEATABLE READ 등)을 활용하고 동시에 적절한 잠금 쿼리를 명시적으로 작성해야 한다는 점을 반드시 인지해야 합니다. InnoDB는 이러한 요청에 기반하여 내부적으로 필요한 락을 설정함으로써, 애플리케이션 개발자가 보다 안정적인 데이터 처리 환경을 구축할 수 있도록 돕습니다.
'CS' 카테고리의 다른 글
| 스프링 시큐리티 - 전체적인 흐름 (0) | 2025.06.28 |
|---|---|
| Redis가 싱글 스레드로 만들어진 이유 (2) | 2025.06.05 |
| 브라우저에서 www.google.com을 검색하면 일어나는 일 (0) | 2025.05.28 |
| equals()와 hashCode() 재정의 (1) | 2025.05.22 |
| 블로킹 vs 논블로킹, 동기 vs 비동기 (0) | 2025.05.10 |