MySQLで、BMP外の文字を入れる場合には、utf8
ではなくutf8mb4
が必要となります。ただし、ただ書き換えただけではうまくいかない部分がありました。
TL; DR
- MySQLのインデックス長は767バイトまで
- 1文字4バイトの
utf8mb4
では191文字が限界 - その(文字コード/カラム長/インデックス)、必要ですか?
エラーを食らう
モバイル向けのサービスを作ることになったのですが、Unicodeの絵文字入れられるようにすることが必要だろう、ということで、文字コードをutf8mb4
とすることにしました。で、いつものようにマイグレーションを作成してマイグレートをかけたのですが、エラーになってしまいました。
見てみると、インデックスの長さでエラーとなっていました。MySQLのインデックスは最大767 バイトで、RailsやCakePHPのデフォルトで生成される255文字のVARCHAR
では、3バイトのutf8
では255×3=765でほぼぴったり収まります。一方、utf8mb4
ではオーバーしてしまうので、エラーとなりました。
さて、何を削ろう
もちろん、このままではうまくいかないので、何かしらの対処が必要となります。
文字数を削る
767バイト÷4 = 191.75なので、VARCHAR(191)
とすれば確実に収まります。というより、255にしても191にしても、たいてい必要な列幅より長いイメージもあるので、適当な幅まで削ったほうがいいかもしれません。電話番号であれば30桁でも過剰なぐらいですし、郵便番号なら10桁もいりません。氏名欄にしたって、100文字を打ち込むこともないでしょう1。
インデックスを付けない
utf8mb4
で入れて、しかもある程度の長さが必要なものを考えれば、それはフリー入力欄、ということが多いと思います。フリー入力欄に対して、並べ替えの基準にしたり、前方/完全一致で検索をかける、というような用事がなければ、インデックスは不要です。
文字コードを入れ替える
インデックスを付けるような使い方をするVARCHAR
カラムは、電話番号、郵便番号などのような何かしらのコード体系に則ったもの、ということもよくあります。そのような場合、必要な文字が足りるのであればlatin1
などを選ぶのも1つの方法です。なお、MySQLの仕様では、「Unicode文字列のカラム」と「非Unicodeのカラム」で演算を行うことはできますが(Unicodeに寄せる)、「utf8
のカラム」と「utf8mb4
のカラム」で演算を行おうとすると、明示的にコードを指定しない限りエラーとなるので、システム中にこの2つを混在させることはおすすめできません(5.6のマニュアル §10.1.7.5 式の照合順序)。
おまけ:CakePHP3でのコレーション指定
CakePHP3のマイグレーションに使われるPhinxでは、何も指定しないとutf8
で動きます。別なコレーションを指定したい場合、$this->table
の第2引数で['collation'=>'utf8mb4_unicode_ci']
のように書く必要があります(StackOverflow)。