MySQLのクライアントエンコーディング
森川です。
前回はマルチバイト関連でしたが、今回もマルチバイトです。前回ソースを解析するなどと言ってしまいましたが、中々難しくまだ途中です。決してあきらめたわけではないので、もう少しすればここに書けるようになると思います。
さて、今 回はMySQLの文字コードについてです。4.1から文字コードの設定が色々と増えたので、設定をきちんとしていなくて問題が起きることも多かったのではないでしょうか。まずは簡単におさらいです。
CentOS4.4のパッケージを使用している場合はデフォルトの文字コードは以下のようになります。
mysql> show variables like 'character%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | latin1 |
| character_set_connection | latin1 |
| character_set_database | latin1 |
| character_set_results | latin1 |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
7 rows in set (0.00 sec)
たとえば、ujisを使用するのであれば、character_set_system以外の文字コードをujisにしておきたいところです。(character_set_system は man mysqld によると識別子を保存するための文字コードで、常にutf8とのことです)
一番簡単なのは、MySQLコンソールから以下のSQLを発行することでしょう。
mysql> SET NAMES ujis;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'character%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | ujis |
| character_set_connection | ujis |
| character_set_database | latin1 |
| character_set_results | ujis |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
7 rows in set (0.00 sec)
これで残りは character_set_databaseとcharacter_set_serverです。この2つを変更するには/etc/my.cnfなどの設定ファイルのmysqldセクションに以下を追加します。
default-character-set=ujis
あとは、mysqldセクションに init-connect='SET NAMES ujis'を追加したり、mysqldump・mysqlなど各セクションにもdefault-character-set=ujisを設定しておけば、サーバ側の設定ではまず大きな問題はおきないでしょう。
さて、ここらが本題です。
PHPにはmysql_real_escape_stringという関数があります。この関数は使用しているMySQLリソースの文字セットによって正しくエスケープしてくれるものです。
が、しかしなのです。この「MySQLリソースの文字セット」というのは「クライアント側の文字コード」なのです。
先ほどの設定で、character_set_clientというものがありましたが、これはサーバからクライアントに返すときの文字コードになり、あくまでサーバ側の設定なのです。
たとえば、MySQLをShift_JISで運用していたとしましょう(これ自体あまりないとは思いますが。。)。そのときにPHPから「可能」という文字列をMySQLに送りたいとします。そんなときは、mysql_real_escape_stringを使うでしょう。期待される動作は何もエスケープされない、ということです。しかし、実際には「可能\」という文字になってしまうはずです。以下のスクリプトでは最後に「\」が追加されるでしょう。
$link = mysql_connect("localhost", "root");
mysql_select_db("test");
echo mysql_client_encoding() . "\n";
// 文字コードをSET NAMESで変換しようとする
mysql_query("SET NAMES 'sjis'");
// 文字コードは変換されません
echo mysql_client_encoding(). "\n";
$str = mb_convert_encoding("表示", "Shift_JIS", "UTF8");
echo bin2hex($str);
echo "\n";
echo bin2hex(mysql_real_escape_string($str));
ただし、これはクライアント側の文字コードがShift_JISではない場合です。現在の文字コードはmysql_client_encoding関数で確認できます。なお、ソースから入れているわけ ではないのであれば、大抵の場合はlatin1がデフォルトだと思います。
では、このクライアント側の文字コードは設定できるのか、というとmysqlエクステンションではできないのです。。。ではどうするのか、というとmysqliエクステンションを使いましょう。
$link = mysqli_connect("localhost", "root");
mysqli_select_db($link,"test");
echo mysqli_client_encoding($link) . "\n";
mysqli_query($link, "set names 'sjis'");
// 文字コードを変更することができる
mysqli_set_charset($link, "sjis");
// ここでは実際に文字コードが変わっていることがわかる
echo mysqli_client_encoding($link). "\n";
$str = mb_convert_encoding("表示", "Shift_JIS", "UTF8");
echo bin2hex($str);
echo "\n";
echo bin2hex(mysqli_real_escape_string($link, $str));
mysqliにはちゃんとmysqli_set_charsetという関数があり、それを使えばきちんとセットすることができます。ちなみに mysql エクステンションから mysqli エクステンションに変更するためのアプリケーションもあります。
これをmysqlエクステンションで実行するにはどうすればよいのか、というとそれほど難しいことではなく、MySQLで提供されているmysql_set_character_set関数を使用するだけです。そのための関数を強引に、PHPのソースコードext/mysql/php_mysql.cに入れてやれば、問題なく変更するための関数を追加できました。あくまで実験なので、mysqliエクステンションを使いましょう。さらに言えば、Shift_JISをデータベースのエンコーディングとして使用するのはやめましょう。
#mysqliエクステンションにクライアント側の文字コードを変更する関数があることをしらずに、mysql自体に備わってないのかと、ソースダウンロードしたり勉強にはなったが無駄な時間を過ごしてしまった。