본문 바로가기
Database

[Database] MySQL 잠금(Lock) InnoDB 스토리지 엔진 잠금 편

by doodoom 2023. 1. 12.

0. 이 글을 쓰게 된 이유

MySQL 엔진 레벨 잠금 편에이어서 InnoDB 스토리지 엔진 잠금 편을 쓰게 되었다. InnoDB 스토리지 엔진은 MySQL에서 기본 스토리지 엔진이며 엄청나게 특수한 경우가 아니면 MyISAM이나 MEMORY 엔진을 사용할 이유가 없다. 그래서 InnoDB 스토리지 엔진만이 레코드 기반 잠금을 제공하기 때문에 스토리지 엔진에서 InnoDB 스토리지 엔진만을 다루게 되었다.

1. InnoDB 스토리지 엔진 잠금

1.1 InnoDB 스토리지 엔진 잠금의 장점

InnoDB 스토리지 엔진은 MySQL에서 제공하는 잠금과는 별개로 스토리지 엔진 내부에서 레코드 기반 잠금 방식을 탑재하고 있다. 이러한 이유로 다음과 같은 장점이 존재한다.

  • InnoDB 스토리지 엔진은 레코드 기반 잠금 방식 때문에 데이터 변경 시 테이블 락을 사용하는 MyISAM보다 훨씬 뛰어난 동시성 처리를 제공할 수 있다.
  • 잠금 정보가 상당히 작은 공간으로 관리되기 때문에 레코드 락이 페이지 락으로, 또는 테이블 락으로 레벨업되는 경우(락 에스컬레이션)는 없다.

1.2 InnoDB 잠금의 종류

InnoDB 잠금은 단순히 레코드만 잠구는 것이 아니라 레코드와 레코드 사이의 간격(갭)도 잠글 수가 있다. 그렇기 떄문에 다양한 락이 존재하게 되는데 이에 대해서 자세하게 알아보자.

1.2.1 레코드 락(Record Lock)

레코드 자체만을 잠그는 것을 레코드 락(Record lock, Record only lock)이라고 며, 다른 DBMS의 레코드 락과 동일한 역할을 한다. 여기서 한가지 중요한 차이는 InnoDB 스토리지 엔진은 레코드 자체가 아니라 인덱스의 레코드를 잠근다는 점이다. 인덱스가 하나도 없는 테이블이더라도 내부적으로 자동 생성된 클러스터 인덱스를 이용해 잠금을 설정한다.
InnoDB에서는 대부분 보조 인덱스(사용자가 생성한 인덱스)를 이용한 변경 작업은 이어서 설명할 넥스트 키 락이나 갭 락을 사용한다. 하지만 프라이머리 키 또는 유니크 인덱스에 의한 변경 작업에서는 갭에 대해서는 잠그지 않고 레코드 자체에 대해서만 락을 건다.

1.2.2 갭 락(Gap Lock)

갭 락은 DB 인덱스 레코드의 갭에 걸리는 Lock이다. 여기서 갭이란 index 중에 DB에 실제 record가 없는 부분이다.


id 컬럼만 있는 테이블이 있고 id에 인덱스가 걸려있다고 하자. 현재 테이블에는 id = 3, id = 7인 데이터만 존재한다.
그러면 4 <= id <= 6에 해당하는 부분에는 인덱스 레코드가 존재하지 않는다. 이 부분이 바로 갭이다.
갭 락은 이러한 갭에 걸리는 락이다. 갭 락의 역할은 레코드와 레코드 사이의 간격에 새로운 레코드가 생성되는 것을 제어한다. 갭 락은 그 자체보다는 이어서 설명할 넥스트 키 락의 일부로 자주 사용된다.

1.2.3 넥스트 키 락(Next key lock)

레코드 락과 갭 락을 합쳐 놓은 형태의 잠금을 넥스트 키 락이라고 한다. 다음 예시를 통해 알아보자.
다음과 같은 쿼리를 실행했다고 가정해보자.

SELECT * WHERE pk > 99 FOR UPDATE


이 과정에서 다음과 같은 일들이 발생한다.

  1. pk > 99를 만족하는 첫번쨰 인덱스 레코드 pk=101을 반견
  2. 첫번째 인덱스 발견 직전의 인덱스 레코드 pk=97부터 pk=101 사이에 갭 락 적용(다른 데이터가 못들어오게 막음)
  3. pk > 99인 모든 인덱스 레코드 사이에도 갭 락 적용
  4. pk > 99인 모든 인덱스 레코드에 레코드 락 적용
    이렇게 갭 락과 레코드 락이 복합적으로 적용되는 것을 넥스트 키 락 이라고 부른다.

InnoDB의 갭 락이나 넥스트 키 락은 바이너리 로그에 기록되는 쿼리가 레플리카 서버에서 실행될 때 소스 서버에서 만들어 낸 결과와 동일한 결과를 만들어내도록 보장하는 것이 주 목적이다. 하지만 의외로 넥스트 키 락과 갭 락으로 인해 데드락이 발생하거나 다른 트랜잭션을 기다리게 만드는 일이 자주 발생한다. 가능하다면 로그 포맷을 ROW 형태로 바꿔서 넥스트 키 락이나 갭 락을 줄이는 것이 좋다. (MySQL 8.0에서는 ROW 포맷이 바이너리 로그 기본 설정이다.)

1.2.4 자동 증가 락(Auto increment lock)

MySQL에서는 자동 증가하는 숫자 값을 추출하기 위해 AUTO_INCREMENT라는 칼럼 속성을 제공한다. 이것이 적용된 칼럼이 있는 테이블에 동시에 여러 레코드가 INSERT되는 경우, 저장되는 각 레코드는 중복되지 않고 저장된 순서대로 증가하는 일련번호 값을 가져야 한다. InnoDB 스토리지 엔진에서는 이를 위해 내부적으로 AUTO_INCREMENT이라고 하는 테이블 수준의 잠금을 사용한다.
AUTO_INCREMENT 락은 INSERT와 REPLACE 쿼리 문장과 같이 새로운 레코드를 제정하는 쿼리에서만 필요하며, UPDATE나 DELETE에서는 걸리지 않는다. InnoDB의 다른 잠금과는 달리 AUTO_INCREMENT 락은 트랜잭션과 관계없이 INSERT, REPLACE 문장에서 AUTO_INCREMENT 값을 가져오는 순간만 락이 걸렸따가 즉시 해제된다.
예를 들어 두개의 INSERT 쿼리가 동시에 실행되는 경우 하나의 쿼리가 AUTO_INCREMENT 락을 걸면 나머지 쿼리는 락을 기다려야한다. 그렇기 떄문에 AUTO_INCREMENT 락은 테이블에 단 하나만 존재한다.
AUTO_INCREMENT 락을 명시적으로 획득하고 해제하는 방법은 없다. AUTO_INCREMENT 락은 아주 짧은 시간동안 걸렸다가 해제되는 잠금이라서 대부분의 경우 문제가 되지 않는다.
여기까지가 MySQL 5.0 이하 버젼에서 사용되던 방식이다(최신 버젼에서도 설정을 통해 사용 가능). MySQL 5.1 이상부터는 innodb_autoincr_lock_mode라는 시스템 변수를 이용해 자동 증가 락의 자동 방식을 변경할 수 있다.
AUTO_INCREMENT 락의 모드와 자세한 설명은 MySQL docs을 참고하기 바란다.