14 October 2008

(Perl + MySQL) 文字列のエンコード UTF-8, Shift-JIS …

MySQLに格納される文字列の標準エンコード形式はUTF-8になっている(MySQL 4.1以降、RHELではutf8がデフォルト)。

Perlでプログラムを作成する時に、UTF-8文字列を扱う方法をリストアップする。

(1)まず、MySQLの文字コードのシステム設定を調べる


mysql> show variables like 'char%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | latin1 |
| character_set_connection | latin1 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | latin1 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)

サーバやDBの文字コードはUTF-8である。(なぜかクライアント側設定がlatinになっている…)

クライアント側の文字コードは、使用しているターミナルの文字コードにあわせないと、英数字以外表示できない状態になっている。試しに、ターミナルでの表示状態を見てみる。


mysql> select idx,name from felica_auth_tbl;
+-----+-------+
| idx | name |
+-----+-------+
| 1 | ??? | ← 日本語の部分が異常
| 2 | Suica |
+-----+-------+
2 rows in set (0.00 sec)

mysql> set names utf8;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like 'char%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)

mysql> select idx,name from felica_auth_tbl;
+-----+-----------+
| idx | name |
+-----+-----------+
| 1 | 八達通 | ← 正常に表示
| 2 | Suica |
+-----+-----------+
2 rows in set (0.00 sec)


(2)PerlスクリプトをSJISで記述する場合


#!/usr/bin/perl
use strict;
use CGI;
use DBI;
use Encode;

# MySQL接続用変数
my $strSqlDsn = 'DBI:mysql:database=auth_db;host=localhost;port=3306';
my $strSqlUser = 'authuser';
my $strSqlPassword = 'authpassword';

# MySQL接続
my $dbh = DBI->connect($strSqlDsn, $strSqlUser, $strSqlPassword);
if(!$dbh) { die('DB connection error'); }

# クライアント側文字コードの指定
$dbh->do("set names sjis");

# SQLクエリ文
my $sth = $dbh->prepare("SELECT name FROM felica_auth_tbl WHERE idm_pmm = '01020304'");

# SQLクエリ実行
if(!$sth->execute())
{
$dbh->disconnect();
die('DB Query Error');
}

# 読み出されたデータ数が1であることをチェック
my $nRows = $sth->rows;
if($nRows != 1)
{
$sth->finish();
$dbh->disconnect();
die('DB data count is not 1');
}

# 1個目のデータを取り出す
my $arrayData = $sth->fetchrow_arrayref;
my ($strUserName) = @$arrayData;

# MySQL切断
$sth->finish();
$dbh->disconnect();

print "ユーザ名: " . $strUserName;


(3)PerlスクリプトをUTF-8Nで記述する場合

(2)のプログラムの、MySQL文字コード指定部分のみ変更すればよい。以下、プログラムの変更部分の抜粋


~ 略 ~

# MySQL接続
my $dbh = DBI->connect($strSqlDsn, $strSqlUser, $strSqlPassword);
if(!$dbh) { die('DB connection error'); }

# クライアント側文字コードの指定
$dbh->do("set names utf8");

~ 略 ~

(4)PerlスクリプトをUTF-8Nで記述するが、MySQLのクライアント文字コードをSJISにする場合

この場合は、Encode::from_to関数で文字コードを変換します。


~ 略 ~

# MySQL接続
my $dbh = DBI->connect($strSqlDsn, $strSqlUser, $strSqlPassword);
if(!$dbh) { die('DB connection error'); }

# クライアント側文字コードの指定
$dbh->do("set names sjis");

~ 略 ~

Encode::from_to($strUserName, 'sjis', 'utf8');
print "ユーザ名: " . $strUserName;

(5)PerlスクリプトをUTF-8Nで記述し、use utf8; 宣言を行う場合

外部からやってきた文字列には、たとえそれがUTF-8エンコードされていたとしても、UTF-8フラグを強制的に立ててやる必要がある。
※ 『 use encoding 'utf8'; 』 の場合は、フラグを立てる処理は必要が無い。


#!/usr/bin/perl

use strict;
use CGI;
use DBI;
use Encode;
use utf8;

~ 略 ~

# クライアント側文字コードの指定
$dbh->do("set names utf8");

~ 略 ~

print "ユーザ名: " . Encode::decode('utf8', $strUserName);


(6)その他、UTF-8関連で不具合出そうなもの

スクリプト先頭で 『 use encoding 'utf8'; 』 を行った場合でも、MySQLから返される文字列がSJISの場合は、Encode::from_to関数で変換すれば問題ない。 『 use utf8; 』 を指定した場合は、SJISの文字列は正しく扱われない。

参考リンク

Perl 5.8.x Unicode関連
【MySQLウォッチ】第36回 文字化けのメカニズム