auto increment을 처음봤을 때 이름 그대로 키 값을 알아서 자동으로 올려주는 기능으로만 생각했다.
그런데 막상 사용하려니 동시 요청에 대해 auto increment는 어떻게 대처하는지 원리를 이해못했다.
여기저기 찾아보니 알아둬야 할 내용들이라 생각하여 정리한다
AUTO INCREMENT란
- AUTO_INCREMENT 속성을 사용하여 새로운 행에 unique ID 생성
- 중복되지 않기 때문에 PK에 AUTO_INCREMENT를 많이 설정한다.
CREATE TABLE animals (
id MEDIUMINT NOT NULL AUTO_INCREMENT,
name CHAR(30) NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO animals (name) VALUES
('dog'),('cat'),('penguin'),
('lax'),('whale'),('ostrich');
SELECT * FROM animals;
+----+---------+
| id | name |
+----+---------+
| 1 | dog |
| 2 | cat |
| 3 | penguin |
| 4 | lax |
| 5 | whale |
| 6 | ostrich |
+----+---------+
- 어떤 값도 id에 설정하지 않았음에도 숫자 1부터 값이 들어간 것을 알 수 있다.
- 또는 id에 0 또는 NULL을 설정해도 auto increment로 동작을 한다.
( 만약 id를 0부터 시작하고 싶다면 SQL mode에서 NO_AUTO_VALUE_ON_ZERO 를 설정할 수 있다. 하지만 SQL mode를 변경하면 데이터 손실이 발생할 수 있기 때문에 주의해야 한다. 또한 보통은 1부터 시작하는 경우가 많다고 한다. )
INSERT 후 auto increment
- 새로운 값이 추가하면, auto increment 대상 ID 컬럼에 auto increment 값이 추가된다. 그리고 다시 해당 컬럼에서
(가장 큰 값 + 1)로 auto increment가 설정
- upate, delete 문 또한 동일하게 동장한다. 하지만 update, delete 후 가장 큰 값이 기존의 가장 큰 값보다 작으면 auto increment는 변경되지 않는다.
mysql> INSERT INTO animals (id,name) VALUES(100,'rabbit');
mysql> INSERT INTO animals (id,name) VALUES(NULL,'mouse');
mysql> SELECT * FROM animals;
+-----+-----------+
| id | name |
+-----+-----------+
| 1 | dog |
| 2 | cat |
| 3 | penguin |
| 4 | lax |
| 5 | whale |
| 6 | ostrich |
| 7 | groundhog |
| 8 | squirrel |
| 100 | rabbit |
| 101 | mouse |
+-----+-----------+
현재 auto increment는 102를 가리키게 된다.
- update 구문 예시
SELECT * FROM animals;
+----+------+
| id | name |
+----+------+
| 3 | dog2 |
| 4 | cat2 |
+----+------+
UPDATE animals WHERE id = 4 SET id = 6;
SELECT * FROM animals;
+----+------+
| id | name |
+----+------+
| 3 | dog2 |
| 6 | cat2 |
+----+------+
show create table animals;
+---------+-----------------------------------------
| Table | Create Table |
+---------+-----------------------------------------
| animals | CREATE TABLE `animals` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(30) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
>> id가 기존 AUTO_INCREMENT(5)보다 큰 값(6)으로 update 이후
>> AUTO_INCREMENT 값이 7로 변경된 것을 확인
UPDATE animals WHERE id = 6 SET id = 4;
INSERT INTO animals(name) VALUES ('mouse');
SELECT * FROM animals;
+----+-------+
| id | name |
+----+-------+
| 3 | dog2 |
| 4 | cat2 |
| 7 | mouse |
+----+-------+
>> id가 기존 AUTO_INCREMENT보다 작은 값으로 update 이후 변경이 없는 것을 확인
INSERT + ROLLBACK 후 auto increment
- auto increment 값은 롤백대상이 아니다.
- 다음 예제에서 롤백되지 않기 때문에 auto increment 값은 기존 순번 그대로 적용된다.
START TRANSACTION;
INSERT INTO animals(name) values ('dog1'), ('cat1');
ROLLBACK;
INSERT INTO animals(name) values ('dog2'), ('cat2');
COMMIT;
SELECT * FROM animals;
+----+------+
| id | name |
+----+------+
| 3 | dog2 |
| 4 | cat2 |
+----+------+
MySQL 재시작시 카운트 초기화(InnoDB)
- MySQL 5.7 버전
- InnoDB 스토리지 엔진의 경우 auto_increment 카운트에 대한 정보를 디스크가 아닌 InnoDB 메모리에 저장
- MySQL 재시작 하게 되면, 재시작 이후 auto_increment 포함된 테이블에 InnoDB는 다음 SQL을 통해 초기화
SELECT MAX(컬럼명) FROM 테이블명 FOR UPDATE;
- 이런 방식에서의 문제는 레코드의 삭제나 rollback 이 있었을 경우 재시작 이후 auto_increment 의 max 값이 달라질 수 있다.
- MySQL 8.0 부터 동작이 변경
- auto_increment의 자동 증가 카운터 값은 변경될 때마다 리두 로그에 기록되고, 체크포인트 발생 시 데이터 딕셔너리에 저장
- 그래서 이러한 방식의 변경은 현재 최대 자동 증가 카운트 값에 대해서 다시 시작해도 지속 됨
InnoDB의 AUTO_INCREMENT 처리
- InnoDB란
- MySQL을 위한 데이터베이스 엔진 (DBMS가 CRUD참조에 사용하는 소프트웨어)
- MySQL 5.5 버전 이후 기본적으로 MyISAM 대신 InnoDB 사용
- MySQL 바이너리에 내장되어 있음
- 트랜잭션 지원, 동시성 제어 가능, 데이터 무결성 보장, 제약조건 및 외래 키 지원
- Row-level Lock (행 단위 Lock) 사용으로 변경 작업(INSERT, UPDATE, DELETE)의 속도가 빠름
- innodb_autoinc_lock_mode 시스템 변수를 설명하기 앞서 INSERT 관련 용어 설명
- “INSERT-like” statements
- 새로운 row를 생성하는 모든 구문
- (아래 3가지 구문 모두 포함)
- “Simple inserts”
- 몇 줄이 insert 될지 실행 전에 미리 알 수 있는 구문
- 여러 줄을 한 번에 insert 하더라도 미리 몇 줄이 insert 될지 알 수 있다면 bulk insert가 아닌 simple insert
- nested subquery가 없는 INSERT, REPLACE,
- INSERT ... ON DUPLICATE KEY UPDATE는 포함되지 않음
- “Bulk inserts”
- 몇 줄이 insert 될지 미리 알 수 없는 구문
- INSERT ... SELECT
- REPLACE ... SELECT
- LOAD DATA
- InnoDB는 한 row가 처리될 때마다 AUTO_INCREMENT 컬럼의 값을 증가
- 몇 줄이 insert 될지 미리 알 수 없는 구문
- “Mixed-mode inserts”
- “simple insert”이긴 하지만 insert구문 내에 AUTO_INCREMENT 컬럼의 값을 일부만 명시적으로 지정해 준 경우.
- 다음 tx에서 c1을 1, 5인 경우만 명시적으로 지정한 예시
- “INSERT-like” statements
INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');
innodb_autoinc_lock_mode 시스템 변수 값에 따른 3가지 lock mode
- traditional: 0 / consecutive: 1 / interleaved: 2
- MySQL 8.0 이후 디폴트 모드: interleaved
- MySQL 8.0 이전 디폴트 모드: consecutive
- “traditional” lock mode
- innodb_autoinc_lock_mode = 0
- traditional lock mode는 이전 버전과의 호환성 및 성능 테스트 용도로도 사용됨
- 모든 “INSERT-like” 구문은 AUTO_INCREMENT 컬럼에 데이터 추가 시 table-level AUTO-INC lock을 가짐
- table-level AUTO-INC lock은 statement 단위다. (transaction단위 아님) 따라서 해당 구문의 실행까지 테이블에 유지되는 lock이다.
- 구문의 순서가 유지되는 한 statement-based replication은 안전하다
- lock이 statement가 끝날 때까지 유지되기 때문에 auto increment 값은 연속적이지만, 동시성에 제약이 있다.
- 다음 예제에서 Tx1실행 후 Tx2 순서로 실행된다. Tx2가 매우 작은 양의 데이터를 INSERT 한다면 순서에 의해 Tx1이 끝날 때까지 기다리게 된다.
Tx1: INSERT INTO t1 (c2) SELECT 1000 rows from another table ...
Tx2: INSERT INTO t1 (c2) VALUES ('xxx');
- “consecutive” lock mode
- innodb_autoinc_lock_mode = 1
- “bulk inserts” (INSERT 행 수를 알 수 없음)
- AUTO-INC table-level lock을 구문 끝날 때까지 유지
- buik 양이 과다하면 테이블을 기다리는 요청이 많아지고 경쟁이 심해지게 된다.
- "Simple inserts"(INSERT 행 수를 미리 암)
- 기본적으로 뮤텍스(a light-weight lock)의 제어하에 필요한 수의 auto-increment 값을 획득하여 auto-increment 값 할당할 때까지만 lock을 유지한다. 그렇기에 concurrency를 높일 수 있다.
- 하지만 다른 transaction의 락으로 인해 테이블을 사용 못하면, AUTO-INC lock을 사용한다.
- 구문의 순서가 유지되는 한 statement-based replication은 안전하다.
- “Mixed-mode inserts”
- auto-increment 값을 사용자가 명시적으로 지정하기 때문에 InnoDB는 삽입할 행 수보다 더 많은 auto-increment 값을 할당하게 된다.
- 다음 예시에서 명시적으로 값을 넣은 후 추가 데이터를 넣어 확인해 보면 auto-increment 값이 삽입한 행 수보다 큰 것을 확인할 수 있다.
mysql> SELECT * FROM ANIMALS;
+----+------+
| id | name |
+----+------+
| 1 | dog1 |
| 2 | dog2 |
+----+------+
mysql> SHOW CREATE TABLE animals;
| animals | CREATE TABLE `animals` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(30) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
mysql> INSERT INTO animals(id, name)
VALUES (3, 'dog3'), (4, 'dog4'), (5, 'dog5'), (null, 'dog6');
// id에 명시적으로 3, 4, 5를 추가
mysql> SELECT * FROM ANIMALS;
+----+------+
| id | name |
+----+------+
| 1 | dog1 |
| 2 | dog2 |
| 3 | dog3 |
| 4 | dog4 |
| 5 | dog5 |
| 6 | dog6 |
+----+------+
mysql> INSERT INTO animals(id, name) VALUES (0, 'dog7');
mysql> SELECT * FROM ANIMALS;
+----+------+
| id | name |
+----+------+
| 1 | dog1 |
| 2 | dog2 |
| 3 | dog3 |
| 4 | dog4 |
| 5 | dog5 |
| 6 | dog6 |
| 10 | dog7 |
+----+------+
// 7이 아닌 10이 설정
// 이유는 명시적으로 정한 값과 auto-increment값이 만나게 되면 충돌을 피하고자,
// 다음 auto-increment 값이 더 커지게 된다.
- “interleaved” lock mode
- 해당 모드부터 “INSERT-like” 구문은 테이블 단위 AUTO-INC락을 잡지 않는다. 그렇기 때문에 다수의 구문이 동시에 실행이 가능하다.
- 대신 consecutive 하지 않아 구문 기반 replication 또는 recovery 시 안전하지 않다.
- 해당 모드로 변경 시 로직 중에 AUTO-INCREMENT 값이 연속된다는 가정이 없는 것을 확인필요
- MySQL 8.0부터 기본값으로 설정
- 이유는 MySQL 5.7의 기본 replication 유형이 Statement-based replication에서 row-based replication로 변경되었기 때문이다.
- Statement-based replication은 SQL 문의 실행 순서에 민감하므로, 연속(consecutive) 자동 증가 잠금 모드를 사용하여 주어진 SQL 문의 시퀀스에 대해 예측 가능하고 반복 가능한 순서로 자동 증가 값이 할당되도록 보장
- row-based replication은 SQL 문의 실행 순서에 영향을 받지 않기 때문에 테이블 단위 락 필요하지 않음
- 라인의 기술블로그에서 모드 설정을 2로 변경한 것만으로도 CPU 100% 사용률이 10~20% 사이로 떨어졌다는 자료도 있다.
참고
https://dev.mysql.com/doc/refman/8.0/en/example-auto-increment.html
https://dev.mysql.com/doc/refman/8.0/en/innodb-auto-increment-handling.html
https://hoing.io/archives/1289#1_consecutive_lock_mode
https://engineering.linecorp.com/ko/blog/how-line-openchat-server-handles-extreme-traffic-spikes
'CS > DB' 카테고리의 다른 글
InnoDB의 Buffer Pool, MVCC (0) | 2024.11.18 |
---|---|
MySQL) 타입 DateTime 과 Timestamp 중 어떤걸 쓸까 (0) | 2023.09.28 |
mysql INFORMATION_SCHEMA 업데이트 속도 (0) | 2023.06.10 |
like 절 주의할 점 (0) | 2022.07.22 |
맥 mysql 설치 및 실행 (0) | 2022.07.22 |