MySQL InnoDB가 존재하는 행뿐 아니라 행과 행 사이의 '틈'까지 잠그는 락. Repeatable Read에서 phantom을 막는 수단이다.
왜 틈을 잠그나
phantom = 조건에 맞는 '새 행이 나타나는' 현상
문제: 아직 존재하지 않는 행은 락을 걸 대상이 없다 해법: 행이 들어올 '자리'를 미리 잠근다
-- amount 100~200 범위의 틈까지 잠근다 → 그 범위로 INSERT 하려는 쪽이 대기한다
SELECT * FROM orders WHERE amount BETWEEN 100 AND 200 FOR UPDATE;
기존 행: [50] [150] [300]
└── 틈 ──┘ └───── 틈 ─────┘
↑ 여기도 잠근다
amount=120 INSERT 시도 → 대기 (phantom 차단)
넥스트키 락은 행 락 + 그 앞의 갭 락을 묶은 것으로, InnoDB가 범위 조건에서 기본으로 잡는 형태다.
실무에서 자주 데는 곳
① 인덱스가 없으면 잠기는 범위가 폭발한다.
-- amount 에 인덱스가 없으면?
SELECT * FROM orders WHERE amount BETWEEN 100 AND 200 FOR UPDATE;
-- → 어디가 조건에 맞는지 모르니 사실상 테이블 전체를 훑으며 잠근다
갭 락은 인덱스 위에 걸린다. 인덱스가 없으면 훨씬 넓은 범위가 잠겨 데드락과 대기가 급증한다. "락 경합이 심하면 인덱스부터 본다"는 조언의 근거다.
② 없는 행을 잠그려다 데드락이 난다.
-- 여러 세션이 동시에 같은 '없는 키'를 INSERT 하려 할 때
INSERT INTO t (uk) VALUES ('same-key'); -- 중복 키 충돌 → 갭 락 대기 → 순환
READ COMMITTED로 낮추면 사라진다
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 갭 락을 거의 안 쓴다
락 경합을 줄이려고 MySQL을 READ COMMITTED로 운영하는 팀이 많은 이유다. 대신 phantom을 애플리케이션이 감당해야 한다.
PostgreSQL에는 없다
PostgreSQL은 갭 락이 없다. Repeatable Read = 스냅샷이라 새 행이 애초에 안 보이므로 잠글 필요가 없다. 같은 목표, 다른 메커니즘이다.
면접 함정
- ❌ "갭 락은 PostgreSQL에도 있다" → 없다. 스냅샷으로 해결한다.
- ❌ "인덱스는 조회 성능 문제일 뿐" → 갭 락 범위를 결정하므로 동시성에도 직접 영향을 준다.
실제로 무엇이 잠겼는지 보기
-- MySQL 8.0+: 락의 종류까지 나온다
SELECT object_name, index_name, lock_type, lock_mode, lock_status, lock_data
FROM performance_schema.data_locks;
-- lock_mode: X,GAP 갭 락만
-- lock_mode: X 레코드 락만
-- lock_mode: X,REC_NOT_GAP 레코드만 (갭 제외)
-- lock_mode: X (넥스트키) 레코드 + 앞의 갭
lock_data에 잠긴 키 값이 찍히므로 의도한 범위보다 넓게 잠겼는지 바로 확인할 수 있다.
인덱스가 없을 때 얼마나 넓어지나
-- amount 에 인덱스가 없는 상태
BEGIN;
UPDATE orders SET status='X' WHERE amount = 150;
-- data_locks 를 보면 사실상 모든 행 + 모든 갭이 잠겨 있다
-- → 다른 세션의 거의 모든 쓰기가 막힌다
"UPDATE 한 건인데 서비스 전체가 멈췄다" 의 정체가 이것이다. 조건 칼럼에 인덱스가 없으면 InnoDB는 어디가 조건에 맞는지 모르므로 훑는 모든 것을 잠근다.
유니크 인덱스는 갭 락을 안 쓴다
-- 유니크 인덱스에 등치 조건이고 행이 존재하면 → 레코드 락만 (갭 없음)
SELECT * FROM member WHERE id = 42 FOR UPDATE; -- 갭 락 없음
-- 단, 행이 '없으면' 갭 락이 걸린다 → 동시 INSERT 데드락의 원인
SELECT * FROM member WHERE id = 999999 FOR UPDATE; -- 갭 락
이 비대칭이 "있으면 되는데 없으면 데드락" 이라는 재현 어려운 버그를 만든다.