@kyanny's blog

My thoughts, my life. Views/opinions are my own.

MySQLの文字コード自動変換とDBD::mysql

http://wota.jp/ac/?date=20061011
http://blog.cheki.net/archives/349
http://www.y2sunlight.com/ground/?MySQL4.1%2F9.MySQL%A4%CE%BC%C2%B8%B3%2F1.%A5%B7%A5%B9%A5%C6%A5%E0%CA%D1%BF%F4%A4%CE%BB%B2%BE%C8%A4%C8%CA%D1%B9%B9#content_1_8
ビンゴ中西のほげほげ2007年11月11日

上から順番に読んでいけばだいたい解決する。はず。

二行でまとめると、

文字コード関連で重要な設定は default-character-set と skip-character-set-client-handshake の二つ。
文字コードの設定がどうなっているかを調べるには status と show variables like 'char%' を使う。

少し補足。 utf8 でビルドされた MySQL だとする。

[client]
default-character-set = ujis
[mysqld]
default-character-set = ujis

とやっていても、 DBI (DBD::mysql) から接続したときに ujis になっているとは限らない。というか、ならない。
文字コード設定を調査するためにこんなスクリプトを書いてみると、

#!/usr/local/bin/perl
use strict;
use DBI;
my $dbh = DBI->connect('dbi:mysql:test;hostname=127.0.0.1','user','password');
my $sth = $dbh->prepare(q{show variables like 'char%'});
$sth->execute;
while (my $row = $sth->fetchrow_arrayref) {
        print sprintf("%s => %s\n", $row->[0], $row->[1]);
}
$sth->finish;

手元の環境ではこんな風に出力された。

character_set_client => utf8
character_set_connection => utf8
character_set_database => ujis
character_set_filesystem => binary
character_set_results => utf8
character_set_server => ujis
character_set_system => utf8

つまり、この設定で euc-jp のバイト列を DBI (DBD::mysql) から挿入しようとすると、 character_set_connection -> character_set_database 間で utf8 -> ujis へと自動変換されるが、もともと utf8 ではなくて euc-jp なバイト列だったのだから変換はうまくいかない。で、化ける。

Perl で書くと、こんな風にしてしまっているのだと思う。

use strict;
use utf8;
use Encode;
my $str = '餃子の王将'; #flagged UTF-8
my $euc = encode('euc-jp', $str);
my $broken_str = encode('euc-jp', decode('utf8', $euc));

で、これを解決するにはクライアント側の文字コード設定を正しく、この場合 ujis に変更してやればいいのだが、 DBD::mysql の perldoc などを見た感じだとオプションとかではできなそうな感じ。いちいち SET ... とかやるのも大変。そこで skip-character-set-client-handshake を [mysqld] セクションに追記すると、クライアントがどんな文字コード設定をもっていようが問答無用で character_set_* を (_system をのぞいて) すべて同じ値に統一してくれる。

[client]
default-character-set = ujis
[mysqld]
default-character-set = ujis
skip-character-set-client-handshake

こういう設定にかえて mysql を再起動し、文字コード調査用スクリプトを実行すると、

character_set_client => ujis
character_set_connection => ujis
character_set_database => ujis
character_set_filesystem => binary
character_set_results => ujis
character_set_server => ujis
character_set_system => utf8

こうなる。 ujis で統一された。これで、文字コードの自動変換はおこらない。この設定で euc-jp のバイト列を挿入しようとすると、バイト列はそのまま ujis なテーブルに保存される。ので、化けない。