43
35

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

utf8mb4で巻き起こったトラブル

Posted at

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)。

  1. 名前が長いことで有名な、パブロ・ピカソの本名で120文字程度です。

43
35
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
43
35

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?