RailsとMySQLでiOSの絵文字に対応(UTF8MB4化)した話
兼ねてからちょこちょこエラーが出ていたiOSの絵文字を含んだデータにようやく対応しましたので、その備忘録です。
Railsは3.2.11、MySQLは5.6.25です。
ちなみにMySQLは5.5以降からUTF8MB4に対応しています。
はじめに
文字コードをUTF8からUTF8MB4に変更をするにあたっていくつかの注意点があります。
コレーション問題
UTF8MB4のコレーションをデフォルトにしたままだと、寿司ビール問題("🍣"と"🍺"が同じものとして扱われる)と言われる問題に遭遇し、コレーションをUTF8MB4_UNICODE_520_CIにすると、ハハパパ問題("ハ"と"パ"が同じものとして扱われる)と言われる問題に遭遇します。
そのため、ハハパパ問題を気にしない場合(そんな国内サービスはあるのか!?)はUTF8MB4_UNICODE_520_CIを、気にする場合は、コードポイントでの比較が可能なUTF8MB4_BINにする必要があります。
しかし、UTF8MB4_BINでは"A"と"a"は別のものとして扱われるので、それを許容する仕様である必要があります。
文字列のバイト長問題
UTF8が1文字あたり3バイトであるのに対し、UTF8MB4は1文字あたり4バイトになります。故に、最大で255バイトまでしか格納できないTINYTEXTカラムでは、 UTF8では85文字まで、UTF8MB4では63文字までしか格納できません。 そのため、63文字以上を格納したい場合は、より大きい型に変更する必要があります。
インデックスのバイト長問題
デフォルトでは最大インデックス長は767バイトですので、UTF8では255文字まで、UTF8MB4では191文字までしかインデックスを張れません。
しかし、こちらはMySQL 5.5.14以降からINNODB_LARGE_PREFIXオプションで最大3072バイトまで拡張できるので、そんなに大きな問題ではないです。
背景
UTF8MB4に対応していない場合、文字列が4バイトコードを含んでいるとそれ以降の部分がMySQL側で自動的にカットされてしまいます。
"てすと🍀" => "てすと"
そのため、nameにユニークキーを貼っているときに、
Hoge.where(name: "てすと🍀").first_or_create
みたいなコードを実行したら、以下のSQLが発行されるので、
select * from hoges where name = "てすと🍀" limit 1; insert into hoges (name) values ("てすと🍀"); # 実際には`name = "てすと"`が生成される
2回目以降のコード実行でWhereの段階では同じレコードが見つからなかったのに、Createの段階では同じレコードが見つかってDuplicationエラーが発生します。
概要
サーバのcharacter_set_*や既存データベース,既存テーブルの設定はなるべく変えたくない(インデックス長とかの諸々問題を考えて)ので、HogeテーブルのName属性の文字コードをUTF8MB4に変更します。
僕の場合は、ハハパパ問題を許容できない仕様だったのでコレーションはUTF8MB4_BINを選択します。
変更方法
まずはじめに、MySQL Clientで文字化けすると確認しづらいので、とりあえずこちらも文字コードをUTF8MB4に変更します。
"my.cnf"ファイル
[client] default-character-set = utf8mb4 [mysqld] character-set-server = utf8mb4
次に、文字コードにUTF8MB4を使用したいカラムの文字コードとコレーションを変更します。
alter table hoges modify column name varchar(80) character set utf8bm4 collate utf8mb4_bin not null;
このとき、Rails側の文字コードとMySQL側のコレーションのベースが一致しないと、下記のようなエラーが発生します。
ActiveRecord::StatementInvalid: Mysql2::Error: Illegal mix of collations (utf8mb4_bin,IMPLICIT) and (utf8_general_ci,COERCIBLE) for operation ..
なので、Rails側の文字コードの設定もUTF8MB4に変更します。
変更ファイルは"config/database.yml"ファイルです。
"config/database.yml"ファイル
development: . encoding: utf8mb4 . test: . encoding: utf8mb4 . staging: . encoding: utf8mb4 . production: . encoding: utf8mb4 .
また、レプリケーションにOctopusを使用している場合は、"config/shards.yml"ファイルも同様に変更する必要があります。
これで実際に絵文字が入っていることが確認できたら完了です。
それにしても、絵文字とそれにまつわる文字コード,コレーションの関係はややこしい・・
参考URL
MySQL :: MySQL 5.6 リファレンスマニュアル :: 10.1.11 以前の Unicode サポートから現在の Unicode サポートへのアップグレード