
MySQL - InnoDB Auto Increment 성능 최적화
MySQL - InnoDB Auto Increment 성능 최적화
MySQL에서 벌크 인서트를 실행할때 내부 동작을 찾아보다 보니, 어찌어찌 MySQL InnoDB 스토리지 엔진 내부에서 AUTO_INCREMENT(오토인크리먼트)를 어떤식으로 핸들링하고 있는지가 더 궁금해져서 MySQL 레퍼런스 문서(MySQL 5.7 기준)를 읽으면서 아래 내용을 요약해보았다.
이제까지 AUTO_INCREMENT 컬럼에 대해서 아무 고민없이 값이 자동으로 잘 증가하겠거니 하고 사용해 왔을텐데, 내부적인 동작 방식을 정확히 이해해서 concurrency를 높여서 성능을 향상시킬 수 있는 튜닝 포인트를 확인해 보자.
인서트의 종류 구분
설명에 들어가기에 앞서 다양한 상황을 좀더 쉽게 설명하기 위해 인서트 구문의 종류를 구분해보면 다음과 같다.
"INSERT-like" statements
- 새로운 row를 생성하는 모든 구문
INSERTINSERT ... SELECTREPLACEREPLACE ... SELECTLOAD DATA
- 아래 명시된 모든 insert 종류들을 포함한다.
- 새로운 row를 생성하는 모든 구문
"Simple inserts"
- 몇줄이 insert될 지 실행전에 미리 알 수 있는 구문
- 여러 줄을 한번에 insert 하더라도 미리 몇줄이 insert될지 알 수 있다면 bulk insert가 아닌 simple insert임에 주의
- nested subquery가 없는
INSERT,REPLACE INSERT ... ON DUPLICATE KEY UPDATE는 포함되지 않는다.
"Bulk inserts"
- 몇줄이 insert될지 미리 알 수 없는 구문
INSERT ... SELECTREPLACE ... SELECTLOAD DATA
- InnoDB가 한줄이 프로세싱될 때마다 AUTO_INCREMENT 컬럼의 값을 증가시켜줌
- 몇줄이 insert될지 미리 알 수 없는 구문
"Mixed-mode inserts"
- "simple insert"이긴 하지만 insert구문 내에 AUTO_INCREMENT 컬럼의 값을 일부만 명시적으로 지정해준 경우. (전체를 명시적으로 지정했다면 "simple insert"라고 볼 수 있음)
- 예:
INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d'); INSERT ... ON DUPLICATE KEY UPDATE- 상황에따라 AUTO_INCREMENT 컬럼에 할당된 값이 사용될 수도, 되지 않을 수도 있는 최악의 케이스
문제 상황
Tx1: INSERT INTO t1 (c2) SELECT 1000 rows from another table ...
Tx2: INSERT INTO t1 (c2) VALUES ('xxx');
위처럼 두개의 트랜잭션이 같은 테이블에 동시에 insert를 할때 AUTO_INCREMENT 컬럼의 값은 어떻게 증가되어야 하는가?
- 테이블 레벨의 lock이 걸리는경우 Tx1이 끝날때까지 Tx2는 실행이 되지 않기때문에 동일 결과가 보장됨
- concurrency가 없기때문에 성능이 좋지 않다.
- 테이블 레벨 lock이 아닌경우 Tx1의 insert가 진행중일때 Tx2가 같이 insert한다면?
- 실행되는 상황에따라서 AUTO_INCREMENT 값이 매번 달라질 가능성이 존재한다.
- 이러한 상황이 문제가 되지 않는 경우를 잘 정의하면 좀더 concurrency를 높여서 성능을 좋게 할 수 있다.
위의 trade-off 관계를 정확히 이해하여 최고의 성능을 이끌어내기 위해 InnoDB에서 지원하는 innodb_autoinc_lock_mode 가 어떤 설정 값들을 지원하는지 아래에서 더 자세히 살펴보도록 하자.
InnoDB가 제공하는 Lock Modes
traditional lock mode
innodb_autoinc_lock_mode = 0- 모든 "INSERT-like" 구문에 대해서 table-level AUTO-INC lock을 적용
- 트랜잭션이 끝날때까지 적용되는 lock이 아닌 해당 구문의 실행시까지만 유지되는 lock
consecutive lock mode (기본 설정값)
innodb_autoinc_lock_mode = 1- "simple insert" 구문에 대해서는 table-level AUTO-INC locks을 회피할 수 있다.
- AUTO_INCREMENT values의 할당 과정에서만 mutex를 이용해서 동시 접근을 제어하기 때문에concurrency를 높일 수 있다 (구문이 실행이 끝날때까지 table-level lock을 적용하는 것 보다 훨씬 효율적임)
- insert시 생성되는 AUTO_INCREMENT 값들은 아래의 한가지 예외 경우를 제외하고는 traditional lock mode를 사용했을때와 결과값이 항상 동일하다.
- 예외: "mixed-mode insert" 인 경우에 InnoDB는 실제 인서트 되어야 하는 rows 수 보다 더 큰 AUTO_INCREMENT 값을 할당한다. 지정된 값을 제외하고, 자동으로 할당된 값의 순차적인 증가는 보장된다.
- interleaved lock mode
innodb_autoinc_lock_mode = 2- "INSERT-like" 구문에 대해서 table-level AUTO-INC lock을 사용하지 않는다.
- concurrency가 가장 높지만 복구/복제용으로 SQL 바이너리 로그 replay를 사용하지 않고있는 경우에만 사용 가능
- AUTO_INCREMENT 값의 의 유니크성, 단조 증가성(monotonic increase)은 보장되지만 동일한 쿼리를 실행하더라도 실행 순서에 따라 매번 rows들이 가지는 AUTO_INCREMENT 값들은 달라질 수 있다. (할당된 AUTO_INCREMENT 값이 구문들 간에 interleaved 될 수 있다.)
- AUTO_INCREMENT 값의 Gap 존재
- "simple inserts"가 수행될때는 할당된 AUTO_INCREMENT 값들에 gap이 존재하지 않는다.
- "bulk inserts"가 수행될때는 gap이 존재 할 수있다.
InnoDB의 AUTO_INCREMENT Counter 초기화 방식
InnoDB 테이블에 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의 값을 수정하는 것도 가능하다. 수동으로 수정된 값이더라도, 서버가 재시작하면 다시 초기화 됨에 유의하자.
AUTO_INCREMENT 카운터의 초기화가 끝난 후 INSERT시 동작
- AUTO_INCREMENT 컬럼에 값이 명시되지 않은 insert를 실행한 경우: counter를 1 증가
- AUTO_INCREMENT 컬럼에 값이 명시된 insert를 실행한 경우: 해당 값이 현재 counter 값보다 크다면 counter를 해당 값으로 업데이트
트랜잭션 상황에서 AUTO_INCREMENT 카운터 증가 실험
위에서 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
상황 가정
- 실행 전 AUTO_INCREMENT = 1
- Transaction isolation level = REPEATABLE READ
- Transaction A, B는 동시에 진행 (각각 교차해서 statement를 실행)
| 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 | - |
결과 분석
- traditional, consecutive, interleaved 각 모드에 대해서 모두 동일한 결과를 얻음(AUTO_INCREMENT를 위해서 statement 단위 lock이 적용되기 때문인 듯)
- 트랜잭션 A, B가 동시에 진행중일때 트랜잭션 내의 쿼리 실행 순서에 따라 AUTO_INCREMENT count가 각각 증가.
- 트랜잭션 마지막에 rollback을 하던 commit을 하던 동일하게 증가된 AUTO_INCREMENT count는 그대로 남아있음 (5 row를 insert 하고난 후에 rollback을 하더라도 AUTO_INCREMENT count는 5가 증가한 상태 그대로)
INSERT 구문이 트랜잭션으로 묶여있는경우, 먼저 시작한 transaction의 INSERT가 완료 될 때 까지 늦게 시작한 트랜잭션의 AUTO_INCREMENT 값이 제대로 반영되지 않을거라는 걱정이 있었지만, AUTO_INCREMENT는 트랜잭션과는 완전 별개로 INSERT구문이 실행될 때마다 각각 증가함을 확인 할 수 있었다.
참고
https://dev.mysql.com/doc/refman/5.7/en/innodb-auto-increment-handling.html
Leave MySQL - InnoDB Auto Increment 성능 최적화 to:
Read more #kr posts
Best Posts From 나는한다개발
We have not curated any of yjiq150's posts yet. But you can encourage our curation team to review posts by visiting them regularly and by referring other readers. Because we give priority to frequently read content.