enum列へのalter table

巨大なtableへのalter tableが必要になったのだけど、enumへのalterって確か最適化してくれたんじゃなかったっけな、ということで、一応確認のため検証してみる

次のschemaを用意します。

            CREATE TABLE `enum_test` (
              `id` int(11) NOT NULL AUTO_INCREMENT,
              `type` enum('foo','bar') DEFAULT NULL,
              PRIMARY KEY (`id`)
            ) ENGINE=InnoDB AUTO_INCREMENT=105135 DEFAULT CHARSET=utf8

次のようなデータを大量にINSERTするプログラムを書き、入れておきます。

            use strict;
            use warnings;
            use DBI;

            my $dbh = DBI->connect('dbi:mysql:test;', 'xxx', 'xxxx', { RaiseError => 1 });
            my $sth  = $dbh->prepare(q{ INSERT INTO enum_test (type) VALUES (?) });
            for (1..100000) {
                for my $type ( qw(foo bar) ) {
                    $sth->execute($type);
                }
            }
            mysql> select count(*) from enum_test;
            +----------+
            | count(*) |
            +----------+
            |   200000 |
            +----------+
            1 row in set (0.05 sec)
            mysql> alter table enum_test modify column type enum('foo', 'bar', 'baz');
            Query OK, 0 rows affected (0.05 sec)
            Records: 0  Duplicates: 0  Warnings: 0

            mysql> alter table enum_test modify column type enum('foo');
            Query OK, 200000 rows affected, 65535 warnings (2.09 sec)
            Records: 200000  Duplicates: 0  Warnings: 100000

            mysql> alter table enum_test add index type(type);
            Query OK, 200000 rows affected (2.71 sec)
            Records: 200000  Duplicates: 0  Warnings: 0

            mysql> alter table enum_test modify column type enum('foo', 'bar', 'baz');
            Query OK, 0 rows affected (0.06 sec)
            Records: 0  Duplicates: 0  Warnings: 0

            mysql> alter table enum_test add index type_id(type, id);

            Query OK, 200000 rows affected (2.81 sec)
            Records: 200000  Duplicates: 0  Warnings: 0

            mysql> alter table enum_test modify column type enum('foo', 'bar', 'baz', 'hoge');
            Query OK, 0 rows affected (0.05 sec)
            Records: 0  Duplicates: 0  Warnings: 0

ということで、このとおり、データに全く入っていないtypeを追加するのは、((複合)INDEXはってようがいまいが)ほぼ一瞬で実行することができます。
一方、既にデータが入っている部分を変更すると、(当然でしょうが)全データに対して実行するので、時間が非常にかかります。


ちなむと、barのデータが入っていたところはどうなるかというと、

            mysql> select * from enum_test limit 10;
            +----+------+
            | id | type |
            +----+------+
            |  1 | foo  |
            |  2 |      |
            |  3 | foo  |
            |  4 |      |
            |  5 | foo  |
            |  6 |      |
            |  7 | foo  |
            |  8 |      |
            |  9 | foo  |
            | 10 |      |
            +----+------+
            10 rows in set (0.00 sec)

こんな感じで、歯脱けになってしまいますね。

追記

mysql> alter table enum_test modify column type enum('foo', 'bar', 'baz', 'hoge', 'fuga', 'fugo', 'gee', 'gaa', 'rere', 'rara', 'garr', 'goo', 'arew', 'rweee');
Query OK, 0 rows affected (0.05 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table enum_test modify column type enum('foo', 'baz', 'bar', 'hoge', 'fuga', 'fugo', 'gee', 'gaa', 'rere', 'rara', 'garr', 'goo', 'arew', 'rweee');
Query OK, 200000 rows affected (3.08 sec)
Records: 200000  Duplicates: 0  Warnings: 0

データがたくさん増えてきたときは、特には問題ないようです。
順番を入れかえてしまうと、数字を振りなおすことになってしまうので、データの書きかえが発生し、時間がかかってしまいます。

まとめ

enumへのalter tableは要素を追加するときに限っては、ほぼ一瞬で変更することができる。
enumは数値で格納され、追加されるデータが非常に小さくなるので、alter tableに時間がかかりそうということを恐れて、varcharに入れとくといったことをする必要性はないと思われる。

enumのカラムは用途にもよるが、INDEXを貼っても要素へのSELECT文のカーディナリティは高くないことも多いので、多態的に用い複数のtypeをまたいで取得といったことをしない場合は、enumに対してlinear partitionなどを適用するとよさげ。

参考

http://dev.mysql.com/doc/refman/5.1/en/alter-table.htmlの"Changing the definition of an ENUM or SET ..."あたりに書いてあるようです。

またまた追記

どうもうまくいかない環境があるなぁ、と思ったら、5.1.40以前の5.1をつかっていたようだ。。。
マニュアルにもマイナーバージョンで追加された機能に関しては、その記述があるとうれしいなぁ。。。(ChangeLog全部嫁って話かもしれないですが。

http://dev.mysql.com/doc/refman/5.1/en/news-5-1-40.htmlの"Appending values to an ENUM or SET definition..."あたり。