MySQL에서 벌크 인서트를 실행할때 내부 동작을 찾아보다 보니, 어찌어찌 MySQL InnoDB 스토리지 엔진 내부에서 AUTO_INCREMENT(오토인크리먼트)를 어떤식으로 핸들링하고 있는지가 더 궁금해져서 MySQL 레퍼런스 문서(MySQL 5.7 기준)를 읽으면서 아래 내용을 요약해보았다.
이제까지 AUTO_INCREMENT 컬럼에 대해서 아무 고민없이 값이 자동으로 잘 증가하겠거니 하고 사용해 왔을텐데, 내부적인 동작 방식을 정확히 이해해서 concurrency를 높여서 성능을 향상시킬 수 있는 튜닝 포인트를 확인해 보자.
설명에 들어가기에 앞서 다양한 상황을 좀더 쉽게 설명하기 위해 인서트 구문의 종류를 구분해보면 다음과 같다.
"INSERT-like" statements
INSERTINSERT ... SELECTREPLACEREPLACE ... SELECTLOAD DATA"Simple inserts"
INSERT, REPLACEINSERT ... ON DUPLICATE KEY UPDATE 는 포함되지 않는다."Bulk inserts"
INSERT ... SELECTREPLACE ... SELECTLOAD DATA"Mixed-mode inserts"
INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');INSERT ... ON DUPLICATE KEY UPDATE
Tx1: INSERT INTO t1 (c2) SELECT 1000 rows from another table ...
Tx2: INSERT INTO t1 (c2) VALUES ('xxx');
위처럼 두개의 트랜잭션이 같은 테이블에 동시에 insert를 할때 AUTO_INCREMENT 컬럼의 값은 어떻게 증가되어야 하는가?
위의 trade-off 관계를 정확히 이해하여 최고의 성능을 이끌어내기 위해 InnoDB에서 지원하는 innodb_autoinc_lock_mode 가 어떤 설정 값들을 지원하는지 아래에서 더 자세히 살펴보도록 하자.
traditional lock mode
innodb_autoinc_lock_mode = 0consecutive lock mode (기본 설정값)
innodb_autoinc_lock_mode = 1innodb_autoinc_lock_mode = 2InnoDB 테이블에 AUTO_INCREMENT 필드를 추가하면, InnoDB의 테이블 정보 저장 공간에 AUTO_INCREMENT counter를 보관한다.
특이하게도 이 counter는 디스크가 아닌 메모리 상에만 보관된다.
MySQL서버가 기동되고난 후 첫번째 insert구문 실행되는 시점에 InnoDB는 아래와 같은 구문을 먼저 실행해서 구한 값에서 1만큼 증가시킨 값을 counter를 메모리상에 로드한다. (auto_increment_increment이 따로 설정된 경우 해당 값 만큼 증가)
SELECT MAX(ai_col) FROM table_name FOR UPDATE;
아래 구문을 실행 할 때도 모든 테이블의 AUTO_INCREMENT 값들을 조회해 오게 되는데, 이 경우에는 각 테이블별로 현재 최대 값을 로드만 하고, 증가시키진 않는다.
SHOW TABLE STATUS
이 상태에서 수동으로 AUTO_INCREMENT counter의 값을 수정하는 것도 가능하다. 수동으로 수정된 값이더라도, 서버가 재시작하면 다시 초기화 됨에 유의하자.
위에서 InnoDB가 제공하는 각각의 AUTO_INCREMENT lock 모드에 대해 concurrent한 다양한 insert들이 일어날때에 대해서 어떻게 AUTO_INCREMENT 값이 변화하는지는 자세히 정리해 보았다. 혹시나 transaction이 걸린 상태에서는 AUTO_INCREMENT 값의 변화가 달라지는지 확인을 해보기 위해서 아래와 같이 추가 실험을 해보았다.
CREATE TABLE test (
id int(11) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id)
) ENGINE=InnoDB
| Transaction A | Transaction B | 쿼리 실행 후 AUTO_INCREMENT 값 |
|---|---|---|
| start transaction; | - | - |
| - | start transaction; | - |
| INSERT INTO test VALUES (); | - | 2 |
| INSERT INTO test VALUES (); | - | 3 |
| - | INSERT INTO test VALUES (); | 4 |
| - | INSERT INTO test VALUES (); | 5 |
| INSERT INTO test VALUES (); | - | 6 |
| commit or rollback | commit or rollback | - |
INSERT 구문이 트랜잭션으로 묶여있는경우, 먼저 시작한 transaction의 INSERT가 완료 될 때 까지 늦게 시작한 트랜잭션의 AUTO_INCREMENT 값이 제대로 반영되지 않을거라는 걱정이 있었지만, AUTO_INCREMENT는 트랜잭션과는 완전 별개로 INSERT구문이 실행될 때마다 각각 증가함을 확인 할 수 있었다.
https://dev.mysql.com/doc/refman/5.7/en/innodb-auto-increment-handling.html