← 아티클 목록

MySQL 갭 락·넥스트키 락 — 데드락과 잠금 대기 진단

2028-01-31#database#MySQL#트랜잭션

분명히 다른 행을 INSERT했는데 데드락이 났거나, 존재하지도 않는 범위에서 Lock wait timeout이 떴다면 범인은 대부분 **갭 락(gap lock)**입니다. InnoDB는 기본 격리수준인 REPEATABLE READ에서 팬텀 읽기를 막기 위해 실제 행뿐 아니라 인덱스 행 사이의 빈 공간까지 잠급니다.

세 가지 잠금 구분

잠금잠그는 대상목적
레코드 락인덱스 레코드 1건그 행 변경 차단
갭 락인덱스 행 사이의 빈 구간그 구간에 INSERT 차단
넥스트키 락레코드 락 + 직전 갭팬텀 방지(기본 동작)

예를 들어 id10, 20, 30인 테이블에서 WHERE id BETWEEN 15 AND 25 FOR UPDATE를 실행하면, 존재하는 20 한 건만이 아니라 1030 사이 구간 전체에 넥스트키 락이 걸립니다. 그래서 다른 트랜잭션이 id = 17을 INSERT하려 하면 대기에 걸립니다.

데드락이 생기는 흐름

가장 흔한 패턴은 두 트랜잭션이 같은 갭에 INSERT하려 할 때입니다.

SQL
-- 트랜잭션 A
SELECT * FROM orders WHERE user_id = 7 FOR UPDATE;  -- user_id 7 없음 → 갭 락
-- 트랜잭션 B
SELECT * FROM orders WHERE user_id = 7 FOR UPDATE;  -- 같은 갭에 공유 갭 락
-- A: INSERT ... user_id = 7  → B의 갭 락 때문에 대기
-- B: INSERT ... user_id = 7  → A의 갭 락 때문에 대기 → 데드락

갭 락은 서로 호환되기 때문에 둘 다 SELECT는 통과하지만, 이후 INSERT에서 교착됩니다.

진단과 완화

  1. 현재 잠금 확인 — 최신 MySQL은 성능 스키마로 본다.
SQL
SELECT * FROM performance_schema.data_locks;
SHOW ENGINE INNODB STATUS\G   -- LATEST DETECTED DEADLOCK 섹션
  1. 인덱스를 정확히 태운다 — 갭 락은 옵티마이저가 스캔한 범위에 비례한다. 조회 조건이 인덱스를 못 타면 더 넓은 구간이 잠긴다. 유니크 인덱스로 단건(WHERE id = 20)을 조회하면 갭 락 없이 레코드 락만 걸린다.

  2. 격리수준 조정READ COMMITTED로 낮추면 넥스트키 락 대신 레코드 락만 사용해 갭 락이 사실상 사라진다. 단 팬텀 읽기는 허용되므로 트레이드오프를 이해하고 적용한다.

  3. 트랜잭션을 짧게 — 잠금 구간을 좁히고, 여러 트랜잭션이 같은 순서로 행에 접근하도록 정렬해 교착 가능성을 줄인다.

요점 정리

  • REPEATABLE READ의 기본 잠금은 넥스트키 락 = 레코드 락 + 갭 락이다.
  • 없는 행을 조회해도 갭은 잠기므로, 같은 갭 INSERT 경쟁이 데드락의 단골 원인.
  • data_locksSHOW ENGINE INNODB STATUS로 실제 잠금을 확인한다.
  • 단건 유니크 조회·READ COMMITTED·짧은 트랜잭션으로 갭 락 영향을 줄인다.

갭 락을 직접 재현하고 data_locks 출력으로 잠금 범위를 확인하는 실습은 데이터베이스 트랙에서 회원가입 없이 무료로 할 수 있습니다.