読者です 読者をやめる 読者になる 読者になる

Akata Works

フルスタックなクリエイターになる・・つもりの二年目Webエンジニアのブログ

RailsとMySQLでiOSの絵文字に対応(UTF8MB4化)した話

Ruby Rails ActiveRecord MySQL 文字コード

兼ねてからちょこちょこエラーが出ていた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 サポートへのアップグレード