Skip to content
난이도 면접 빈출문제은행 7

InnoDB 잠금 ​

InnoDB가 행에 거는 잠금을 다룬다. 잠금의 성격은 공유와 배타 두 가지가 기본이고, 잠그는 범위는 레코드·갭·넥스트키 세 가지가 핵심이다. InnoDB는 행이 아니라 인덱스 레코드(인덱스에 든 항목 하나. 클러스터링 인덱스에서는 행 하나)에 잠금을 걸기 때문에 인덱스와 잠금은 떼어 볼 수 없다. 그래서 적절한 인덱스 없이 UPDATE하면 클러스터링 인덱스(clustered index)를 넓게 스캔하며 거의 모든 레코드를 잠글 수 있다.

종류 두 축: 성격 × 범위 ​

성격으로 나누면 공유 락(shared lock)과 배타 락(exclusive lock)이 있다. 공유 락(S 락)은 읽으려고 거는 잠금이고, 배타 락(X 락)은 고치려고 거는 잠금이다. 아래 표는 어떤 SQL이 어떤 락을 거는지 보여 준다.

읽기쓰기
공유(S)FOR SHARE (LOCK IN SHARE MODE는 구문 호환용 별칭)—
배타(X)FOR UPDATEUPDATE·DELETE

같은 행에 S 락은 여러 트랜잭션(transaction)이 같이 잡을 수 있다. X 락은 한 트랜잭션만 잡을 수 있고, X 락이 걸려 있으면 다른 트랜잭션은 S 락도 잡지 못한다. 범위로 나누면 세 가지다.

  • 레코드 락(record lock): 인덱스 레코드 하나에 건다. "이 행 건드리지 마"라는 뜻이다.
  • 갭 락(gap lock): 인덱스 레코드 사이의 빈 간격에 건다. "여기에 새 행 넣지 마"라는 뜻이다. REPEATABLE READ 격리 수준(isolation level)에서 팬텀 리드(phantom read)를 막는 장치다.
  • 넥스트키 락(next-key lock): 레코드 락과 그 레코드 바로 앞 갭의 갭 락을 합친 것이다. InnoDB가 범위를 잠글 때 쓰는 기본 단위다.
다이어그램 로딩…

그림의 (10, 20]은 10 초과 20 이하, 즉 10과 20 사이의 갭과 레코드 20을 함께 잠근다는 표기다. WHERE id BETWEEN 10 AND 20 FOR UPDATE처럼 읽으면서 잠금을 거는 잠금 읽기(locking read)는 실행 계획(execution plan)이 스캔한 인덱스 구간에 넥스트키 락을 건다. 경계 다음 레코드의 앞쪽 갭까지 포함될 수 있고, 조건에 맞는 행이 없어도 삽입할 위치의 갭은 잠길 수 있다. 정확한 범위는 사용 인덱스와 실제 스캔 범위로 판단한다.

인덱스와 잠금 — 제일 중요한 문단 ​

InnoDB는 스캔한 인덱스 레코드에 잠금을 건다. 조건에 최종적으로 맞는 행만이 아니라, 조건을 확인하려고 훑은 레코드에 건다는 뜻이다. 실행 계획이 풀 테이블 스캔(full table scan)이면 클러스터링 인덱스의 많은 레코드를 방문하고 잠그므로 사실상 테이블 전체가 막힌 것처럼 보일 수 있다. READ COMMITTED에서는 조건에 맞지 않는 레코드의 잠금을 비교적 일찍 해제하지만, 잠금 범위를 줄이는 첫 수단이 적절한 인덱스라는 점은 같다.

인텐션 락(intention lock)은 테이블 층에 붙이는 표시판이다. 트랜잭션은 행에 S 락을 걸기 전에 테이블에 IS(Intention Shared, 인텐션 공유) 락을, X 락을 걸기 전에 IX(Intention Exclusive, 인텐션 배타) 락을 건다. 이 표시판은 "이 테이블 어딘가에 행 잠금이 있다"는 예고다. 그래서 누가 테이블 전체에 거는 테이블 락(table lock)을 걸려 할 때 행을 다 뒤지지 않고 표시판만 보면 충돌 여부를 알 수 있다.

데드락과 확인 ​

  • 데드락(deadlock)은 두 트랜잭션이 서로 상대가 잡은 잠금을 기다리며 둘 다 멈춘 상태다. InnoDB는 이를 자동 감지해 한쪽(보통 변경량 적은 쪽)을 롤백(rollback)시킨다. 롤백은 그 트랜잭션의 변경을 모두 취소하고 잠금을 푸는 것이다. 롤백당한 쪽은 데드락으로 롤백됐다는 에러 1213 Deadlock을 받는다. 이때 트랜잭션을 처음부터 다시 실행하는 재시도 로직으로 응답하는 게 정석이다.
  • 데드락 내역은 SHOW ENGINE INNODB STATUS 출력의 LATEST DETECTED DEADLOCK 섹션(가장 최근에 감지된 데드락)에서 확인한다. 또는 Performance 스키마(Performance Schema)의 performance_schema.data_locks·data_lock_waits 테이블로 잠금과 대기 관계를 본다(조회 예는 온라인 DDL 4·Performance 스키마와 sys 스키마 3).
  • 예방하려면 모든 트랜잭션이 같은 순서로 접근하게 하고(예: 부모 테이블 다음 자식 테이블), 트랜잭션을 짧게 유지하며, 인덱스로 잠금 범위를 좁힌다.

격리 수준과의 관계 ​

  • READ COMMITTED에서는 갭 락을 거의 걸지 않는다. 외래 키(foreign key) 검사와 유니크 키 중복 확인 때만 예외로 건다. 새 행이 들어올 틈을 막지 않으니 동시성이 높은 대신 팬텀 리드가 보인다.
  • REPEATABLE READ는 넥스트키 락으로 범위를 잠가 팬텀 리드를 대부분 막는다. 대가로 데드락과 잠금 대기가 늘 수 있다.
  • SERIALIZABLE은 가장 엄격한 격리 수준이다. 자동 커밋(autocommit)이 꺼져 있으면 일반 SELECT도 잠금 읽기처럼 공유 락을 사용한다. 자동 커밋이 켜진 단일 SELECT는 그 문장 하나가 자체 트랜잭션이 되어, 잠금 없이 한 시점의 데이터를 읽는 일관된 읽기(consistent read)로 처리될 수 있다.

면접 포인트 ​

  • "갭 락이 왜 필요해요?" → 팬텀 리드 방지. 없는 행 사이 삽입을 막아야 범위 조회가 반복 가능해진다.
  • "인덱스 없이 UPDATE하면?" → 풀 테이블 스캔으로 많은 인덱스 레코드를 잠가 사실상 전체가 막힐 수 있다. 실행 계획 분석과 연결해 답한다.
  • "데드락 나면 어떻게 해요?" → 감지는 자동, 당한 쪽은 재시도. 예방은 접근 순서 통일·짧은 트랜잭션.

관련 문서