팬텀 리드(Phantom Read)란 트랜잭션이 동일한 조건의 쿼리를 반복해서 실행할 때 처음 실행했을 때의 결과에서 존재하지 않았던 새로운 행이 나타나는 현상을 말한다. 읽기 일관성을 유지하는 과정에서 발생할 수 있으며, 다른 트랜잭션에서 데이터의 삽입이나 삭제가 발생하는 경우 발생할 수 있다.
-- 트랜잭션 A 시작
START TRANSACTION;
-- 트랜잭션 A 첫 번째 조회
SELECT * FROM orders WHERE amount > 150;
-- 트랜잭션 B 시작
START TRANSACTION;
-- 트랜잭션 B 새로운 행 삽입
INSERT INTO orders (customer_id, amount) VALUES (4, 250);
-- 트랜잭션 B 커밋
COMMIT;
-- 동일한 조건으로 트랜잭션 A 두 번째 조회시, 트랜잭션 A의 첫 번째 조회에는 존재하지 않던, 트랜잭션 B에서 삽입된 새로운 행이 함께 조회된다.
SELECT * FROM orders WHERE amount > 150;
-- 출처 : https://www.maeil-mail.kr/question/93
위 트랜잭션 A에서 값 조회시 처음에는 (4,250) 행이 없었지만 트랜잭션 B가 행을 삽입함으로써 새로운 행이 트랜잭션 A에서 조회된다. 이를 팬텀 리드라 한다.
unrepeatable read와의 차이는?
팬텀 리드와 unrepeatable read를 보며 무엇이 다른지 헷갈리곤 했었다. unrepeatable read는 팬텀 리드와 다르게 한 행에서 값이 조회할 때 달라지는 것을 말한다. 팬텀 리드는 반면에 한 row안에서 값이 달라지는 것이 아닌 새로운 행이 나타나는 것을 말한다.
갭 락
갭 락(Gap Lock)은 특정 인덱스 값 사이의 공간을 잠그는 락으로, 기존 레코드의 일정 범위에 락을 걸어 해당 범위에 새로운 레코드의 삽입을 방지한다. 이렇게 특정 범위 내에 새로운 데이터의 삽입을 막아 팬텀 리드를 방지하는데, 예를 들어 인덱스 값 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);
...
두 번째 명령어로 인해 2와 4 사이인 3에 대해 레코드 락이 걸리고 2가 포함되는 범위인 1-3, 4가 포함되는 범위인 3-5에 갭 락이 걸린다.
레코드 락
레코드 락은 해당 행(레코드)을 잠그는 락을 말한다. 위 명령어로 인해 order_id가 3인 행이 잠겨 수정이 불가능해졌다.
이렇게 한 명령어에 대해 갭 락과 레코드 락이 동시에 걸렸다. 이렇게 갭 락과 레코드 락이 결합한 형태를 넥스트키 락이라 한다.
넥스트키 락
넥스트키 락은 갭 락과 레코드 락이 결합한 형태로, 특정 인덱스 레코드와 주변의 갭을 동시에 잠그는 락이다. 이로써 해당 레코드 자체의 변경과 주변 공간까지 동시에 보호 및 제어가 가능하다. 이로써 팬텀 리드를 방지할 수 있다. 위 코드 또한 갭 락과 레코드 락이 동시에 걸렸으므로 넥스트키 락이 적용되었다고 볼 수 있다.