MySQL 5.5以降のutf8mb4とPerlのDBD::mysqlのmysql_enable_utf8のワナ
結論から言うと、ひろせが望ましいと思う順にこうすればいいんじゃないの?ってのを列挙します。
- my.cnfに[libmysqlclient]グループを追加しそこにdefault-character-set = utf8mb4と書き、DBD::mysqlでは mysql_read_default_file=/etc/my.cnf;mysql_read_default_group=libmysqlclient と指定する
- mysql_enable_utf8を使いたい場合はSET NAMES指定も必要
- my.cnfの[client]グループにdefault-character-set = utf8mb4と書き、DBD::mysqlでは mysql_read_default_file=/etc/my.cnf と指定する。mysqlbinlogなど--default-character-setオプションを解せずエラーになるものは--no-defaultsをつけて実行する
- mysql_enable_utf8を使いたい場合はSET NAMES指定も必要
- DBD::mysqlで接続後に SET NAMES utf8mb4 を実行する
オハマリポイントは
- DBD::mysql (libmysqlclientなクライアント全般?) は loose-default-character-set の設定値を認識しない
- select結果をPerlの内部表現文字列(UTF8フラグ)にしようと DBD::mysql の mysql_enable_utf8 を使うと、ついでにクライアント側も文字コード設定を(utf8mb4ではなく)utf8にしてしまう
です。
DBD::mysql (libmysqlclientなクライアント全般?) は loose-default-character-set の設定値を認識しない
my.cnfの[client]グループに書いた設定はクライアント全般に適用されるので、ここにdefault-character-set = utf8mb4 と書ければいいのですが、[client]グループに書くとmysqlbinlogなどこのオプションに対応していないクライアントがエラー終了してしまいます。
my.cnfの設定項目には loose- というprefixをつけることができて、対応していないクライアントはそれを無視することができます。
なので、[client]グループに loose-default-character-set = utf8mb4 と書いておけば、シアワセになれると思っていたのですが、loose- だとDBD::mysql (libmysqlcientを使うクライアント全般かもしれません)が認識しくれず、libmysqlclientのデフォルト文字コード(特に変えてなければ latin1)になってしまいます(ガーン)。
仕方ないので、[libmysqlclient]といったグループを新規で追加してそこにdefault-character-setを書き、DBD::mysqlにはそのグループを読むように指示するか、mysqlbinlogがエラーになるのは --no-defaults オプションをつけて回避するか、の2択になります。
SET NAMESよりdefault-character-setを推す理由は、エスケープ処理に関連するからです。詳しくはこのへんを参照してください。
select結果をPerlの内部表現文字列(UTF8フラグ)にしようと DBD::mysql の mysql_enable_utf8 を使うと、ついでにクライアント側も文字コード設定を(utf8mb4ではなく)utf8にしてしまう
の通りですが、DBD::mysqlの mysql_enable_utf8 はPerlの文字列の内部表現化(sv_utf8_decode)するだけでなく、mysql_options(MYSQL_SET_CHARSET_NAME) も実行します。
なにが問題になるかというと、mysql_enable_utf8 を 1 にしてネコ (Unicode: U+1F639, UTF-8: \xF0\x9F\x98\xB9) をutf8mb4なテーブルにinsertしても、utf8mb4でなく MYSQL_SET_CHARSET_NAME=utf8 になっているので DBD::mysql::st execute failed: Incorrect string value というエラーでinsertできません。
また、mysql_enable_utf8 を 0 にした場合(MYSQL_SET_CHARSET_NAME=latin1 を実行している)やmysql_enable_utf8を指定しなかった場合(libmysqlclientのデフォルトであるlatin1が使われる)、my.cnfの適切なグループにdefault-character-set = utf8mb4と書いてあれば、character_set_clientや_connectionはutf8mb4になりますが、libmysqlclientのエスケープ処理は(多分)latin1で行われるのでもしかしたら問題があるかもしれません。
(エスケープ処理が本当にlatin1で行われるのかと、その場合具体的にどういう問題があるのか、はちょっと調べきれてないので情報あったら教えてください><)
エスケープの問題を別にすれば、mysql_enable_utf8を有効にしてもしなくてもいずれにせよ、MySQLに接続した直後にSET NAMES utf8mb4を実行すれば、ネコもちゃんとハンドリングできます。
生のDBD::mysqlなら、Callbacksのconnectedでやるといいと思います。
my $dbh = DBI->connect($dsn, $user, $password, { ... Callbacks => { connected => sub { $_[0]->do('SET NAMES utf8mb4'); return; }, }, });