MySQLのトリガでシーケンスでの自動採番を実現する
同じデータベースの違うテーブルでプライマリキーを重複させたくない場合や、
クラスタ化した場合とかで AUTO_INCREMENT が使えない場合。
いわゆる採番テーブルってやつ。
DBICとかで
sub nextval { my $self = shift; my $dbh = $self->result_source->storage->dbh; my $sql = 'UPDATE sequence SET id = LAST_INSERT_ID(id + 1)'; my $sth = $dbh->prepare($sql); $sth->execute; my $id = $sth->{mysql_insertid}; $sth->finish; $id; }
とか手動でやってもよかったんだけど、せっかくなのでMySQLのトリガーでやってみた。
DROP TABLE IF EXISTS sequence; CREATE TABLE sequence (id INT UNSIGNED NOT NULL); INSERT INTO sequence VALUES (100000); DROP TABLE IF EXISTS hoge; CREATE TABLE hoge ( id INT UNSIGNED NOT NULL PRIMARY KEY, content TEXT ); DROP TRIGGER IF EXISTS hoge_set_id; delimiter // CREATE TRIGGER hoge_set_id BEFORE INSERT ON hoge FOR EACH ROW BEGIN UPDATE sequence SET id = LAST_INSERT_ID(id + 1); SET NEW.id = LAST_INSERT_ID(); END; // delimiter ;
とこんな感じで作って、
(root@localhost) [test]> INSERT INTO hoge (content) VALUES ('hoge'); Query OK, 1 row affected, 1 warning (0.01 sec) (root@localhost) [test]> SELECT * FROM hoge; +--------+---------+ | id | content | +--------+---------+ | 100001 | hoge | +--------+---------+ 1 rows in set (0.01 sec) (root@localhost) [test]>
いけた。が、なんか warning でてる。
(root@localhost) [test]> SHOW WARNINGS; +---------+------+-----------------------------------------+ | Level | Code | Message | +---------+------+-----------------------------------------+ | Warning | 1364 | Field 'id' doesn't have a default value | +---------+------+-----------------------------------------+ 1 row in set (0.00 sec)
まぁそうだけどさ・・・BEFOREトリガ呼んだあとに確認してくれないものか。
ダミーデータを入れたら当然出なくなるが、あまり気持ちのいいものではない。
(root@localhost) [test]> INSERT INTO hoge (id, content) VALUES (0, 'hoge'); Query OK, 1 row affected (0.01 sec)
sql/sql_insert.cc を軽く追ったところ、やはりBEFORE INSERT のトリガ呼ぶ前にエラーメッセージをセットしてるっぽい。
まぁ気にしないことにした。
意外とこの方法は使えるかも。割とお手軽だし、アプリケーションに依存しない。
サーバが複数にまたがってる場合は FEDERATED を使えばいけるんじゃないか(試してないけど)。
関係ないが、Data::YUIDはそろそろ使ってみたい。