3
1

More than 1 year has passed since last update.

SELECT+INSERTでAUTO_INCREMENTの値が増える (飛ぶ)

Last updated at Posted at 2021-11-12

概要

MariaDB 10.6.3を使って開発をしていて気になったので、取り敢えずここにまとめる。

AUTO_INCREMENTの値がおかしい...

SELECTしてINSERTしたとき、idの値が飛んだのでなんだろうと思って調べてみる。

テスト環境を用意する

test1テーブル(マスタ情報) と test2テーブル を作成する。

・test1テーブル作成 & データを登録する (4件)

CREATE TABLE `test1` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `name` varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT '名前',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='テスト1';
INSERT INTO `test1` (`name`) VALUES ('太郎'), ('次郎'), ('三郎'), ('四郎');

table-test1.png

・test2テーブル作成

CREATE TABLE `test2` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `name` varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT '名前',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='テスト2';

table-test2.png
これで2つのテーブルができました。

再現してみる

test2テーブルにtest1のデータをSELECTしてINSERTしてみる。

INSERT INTO `test2` (`name`) SELECT `test1`.`name` FROM `test1`;
INSERT INTO `test2` (`name`) VALUES ('五郎'), ('六郎');

data-test1.png
※ 五郎と六郎のIDが飛んでいる...
ddl-test1.png
※ AUTO_INCREMENTは、10にっている...

普通にINSERTしただけなのに、IDが飛んでいるなぜ?

innodb_autoinc_lock_modeについて

どうやら、ちゃんと連番にしたかったら、「innodb_autoinc_lock_mode = 0」にする必要があるみたい。
しかし、パフォーマンスが悪くなるからデフォルトは「innodb_autoinc_lock_mode = 1」らしい。

確認してみる。

SELECT @@innodb_autoinc_lock_mode;

innodb_autoinc_lock_mode.png
デフォルトのまま「innodb_autoinc_lock_mode = 1」のようです。

innodb_autoinc_lock_modeの設定を変更してみる。

mysql.cnf
[mysqld]
innodb_autoinc_lock_mode=0

innodb_autoinc_lock_mode=0.png

再度、test2テーブルにSELECT INSERTしてみる。

TRUNCATE `test2`;
INSERT INTO `test2` (`name`) SELECT `test1`.`name` FROM `test1`;
INSERT INTO `test2` (`name`) VALUES ('五郎'), ('六郎');

data-test2-0.png
※ 五郎、六郎のIDが正常に連番で登録される
ddl-test2-0.png
※ AUTO_INCREMENTも飛ばず、「7」になっている

以前からINSERTでエラーが発生したら、IDが飛ぶのは知っていました。
しかし、SELECT + INSERTしたらIDが飛ぶのは認識していませんでした。

この方法で、IDは飛ばなくなったのですが、パフォーマンスが気になる。
「innodb_autoinc_lock_mode=1」に戻して、他に方法がないか検討してみる。

mysql.cnf
[mysqld]
innodb_autoinc_lock_mode=1

IDを指定してみる

SELECTしてINSERTする際、IDを指定してみる。

TRUNCATE `test2`;

BEGIN;
SET @num = -1;
SET @current = (SELECT `auto_increment` FROM `INFORMATION_SCHEMA`.`TABLES` WHERE table_name = 'test2');
INSERT INTO `test2` (`id`, `name`)
SELECT
  @current + (@num := @num + 1) AS `id`,
  `test1`.`name` 
FROM 
  `test1` 
WHERE 
  `name` IS NOT NULL
;
COMMIT;

INSERT INTO `test2` (`name`) VALUES ('五郎'), ('六郎');

data-test2-1.png
※ 五郎、六郎のIDは連番になりました
ddl-test2-1.png
※ AUTO_INCREMENTも正常な値で飛んでいないようです

IDカラム(auto_incrementのカラム)を直接指定したら、発生しないようです。

PostgreSQLのnextval()関数のようなもので事前に取得できればいいのに...
MySQLにはそのような関数は無さそうなので残念...

INSERT後にAUTO_INCREMENTを更新する

値がおかしくなるのであれば、INSERT後にAUTO_INCREMENTを再設定してみたらどうなるだろうと思い試す。

TRUNCATE `test2`;

BEGIN;
INSERT INTO `test2` (`name`) SELECT `test1`.`name` FROM `test1`;

SET @s = CONCAT('ALTER TABLE `test2` AUTO_INCREMENT=', (SELECT MAX(id) + 1 FROM `test2`));
PREPARE stmt1 FROM @s;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
COMMIT;

INSERT INTO `test2` (`name`) VALUES ('五郎'), ('六郎');

結果は、IDを指定したときと同じ結果になりました。

この方法でもIDが飛ぶのを解消はできそう...

まとめ

IDが飛ばないように試行錯誤してみましたが、あまりいい方法では無い気がする。
どうしてもIDを連番にしてパフォーマンスが落ちてもいいのであれば、素直に「innodb_autoinc_lock_mode=0」に設定するのがいいのかな...

何かいい方法が見つかればいいけど...
連番にすることが必要であれば、要検討ですね。

参考サイト

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1