テーブルのidはAUTO_INCREMENTにすることが多いと思う。AUTO_INCREMENTのカラムはレコードを挿入する度に文字通りインクリメントされるので、基本的には連番になる。
しかし、以下のような操作を行うと連番ではなく飛び飛びになってしまう。
# valueカラムはUNIQUE
$ MariaDB [test]> SHOW COLUMNS FROM auto_increment_test;
+-------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| value | int(10) unsigned | NO | UNI | NULL | |
+-------+------------------+------+-----+---------+----------------+
2 rows in set (0.015 sec)
# 3行挿入
$ MariaDB [test]> INSERT INTO auto_increment_test(value) VALUES (100), (200), (300);
Query OK, 3 rows affected (0.003 sec)
Records: 3 Duplicates: 0 Warnings: 0
# 挿入した結果
$ MariaDB [test]> SELECT * FROM auto_increment_test;
+----+-------+
| id | value |
+----+-------+
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
+----+-------+
3 rows in set (0.000 sec)
# INSERT IGNORE(重複するレコードがある場合は挿入しない)
# valueカラムが100・200・300のレコードは存在するのでINSERTされない
$ MariaDB [test]> INSERT IGNORE INTO auto_increment_test(value) VALUES (100), (200), (300);
Query OK, 0 rows affected, 3 warnings (0.004 sec)
Records: 3 Duplicates: 3 Warnings: 3
# valueカラムが重複しないように3行挿入
$ MariaDB [test]> INSERT INTO auto_increment_test(value) VALUES (400), (500), (600);
Query OK, 3 rows affected (0.003 sec)
Records: 3 Duplicates: 0 Warnings: 0
# id(AUTO_INCREMENT)が飛んで連番ではなくなっている
$ MariaDB [test]> SELECT * FROM auto_increment_test;
+----+-------+
| id | value |
+----+-------+
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
| 7 | 400 |
| 8 | 500 |
| 9 | 600 |
+----+-------+
6 rows in set (0.000 sec)
これは、INSERTの成否に関わらずAUTO_INCREMENTのカラムの採番が行われているから(INSERTされない場合は採番した値は使わない)。
INSERT IGNOREを行うときに発生しやすいが、普通のINSERT文が重複により失敗したときにも発生する。
目次
AUTO_INCREMENTカラムが連番でなくなると何が困るのか?
idの消費ペースが上がる
最も大きな問題はINSERT IGNOREを頻繁に実行するようなシステムだとidの消費がものすごく早くなってしまうこと。
場合にもよるが、符号なしINTだと約42億までしか採番できないので非常に枯渇しやすい。
符号なしBIGINTだと約1800京なので十分に感じる。しかし、年単位でサービスを運営する前提ならこれでも少し不安が残る。
もちろんidの消費ペースによるので、INSERT IGNOREを使いつつもINTで十分なケースもあるはず。
連番でないと気持ち悪い
「連番でないと気持ち悪い」という考えもあるかもしれない。
とはいえ、レコード自体は独立しているものであり各レコードを一意に特定できさえすればidが連番になっていても飛んでいても影響がない。
要は気持ちの問題なので、こちらは無視して良いと思う。
AUTO_INCREMENTの値を連番にする方法
my.confの設定を変える
「INSERTの成否に関わらず採番する」設定は”innodb_autoinc_lock_mode”で行う。
デフォルトだと”innodb_autoinc_lock_mode = 1″だが、この値を0にするとINSERT IGNOREやINSERT失敗時には採番されなくなるのでAUTO_INCREMENTの連番が保証される。
[mysqld]
innodb_autoinc_lock_mode=0
# ログイン
$ mysql -u test -p
Enter password:
# innodb_autoinc_lock_modeの値を確認する
$ SELECT @@innodb_autoinc_lock_mode;
+----------------------------+
| @@innodb_autoinc_lock_mode |
+----------------------------+
| 0 |
+----------------------------+
ただし、もともとパフォーマンスを上げるために”innodb_autoinc_lock_mode = 1″になっているので、この値を変えてしまうとDBのパフォーマンスが低下する可能性がある点には要注意。
14.6.5.2 構成可能な InnoDB の自動インクリメントロック
https://dev.mysql.com/doc/refman/5.6/ja/innodb-auto-increment-configurable.html
※日本語、少し古いので注意
15.6.1.6 AUTO_INCREMENT Handling in InnoDB
https://dev.mysql.com/doc/refman/8.0/en/innodb-auto-increment-handling.html
また、設定ファイルを弄る = プログラムのソースコードを見ただけでは設定が変わっていることに気が付かない(気が付けない)、という点にも注意。
INSERTの前に存在確認をする
INSERTを実行する前に、当該レコードが存在するかどうかを調べる方法もある。
# 当該レコードが存在する場合
$ MariaDB [test]> SELECT COUNT(*) FROM auto_increment_test WHERE value = 100;
+----------+
| COUNT(*) |
+----------+
| 1 |
+----------+
1 row in set (0.001 sec)
# 当該レコードが存在しない場合
$ MariaDB [test]> SELECT COUNT(*) FROM auto_increment_test WHERE value = 900;
+----------+
| COUNT(*) |
+----------+
| 0 |
+----------+
1 row in set (0.001 sec)
当該レコードがないとき(= COUNTの値が0のとき)だけINSERTすれば採番が行われないので無駄なidの消費を抑えることができる。
この方法だとプログラムのソースコード中に”SELECT COUNT(*) …”と記載されるので「存在確認をしている」というのが後から見て分かりやすい。
しかし頻繁に存在確認を実行するようだとそのせいでパフォーマンスが落ちる可能性は考えられるので注意しないといけない。