今さらですけど、自分でもちゃんと把握してなかったので調べてみました。
MySQLのCharsetのうちシフトJIS系のものはsjisとcp932の二つあります。
どちらもコードの範囲は次のように同じです。
1バイト文字 | 0x00-0x7F, 0xA1-0xDF |
2バイト文字の1バイト目 | 0x81-0x9F, 0xE0-0xFC |
2バイト文字の2バイト目 | 0x40-0x7E, 0x80-0xFC |
違いは文字集合です。1バイト文字はどちらも同じ(ASCII + JIS X 0201 カナ)ですが、2バイト文字はsjisはJIS X 0208 で、cp932はWindows-31Jです。
sjisに含まれていない文字
cp932はsjisよりも文字が多く、丸囲み数字(「①」「②」「③」等)、ローマ数字(「Ⅰ」「Ⅱ」「Ⅲ」等)、組文字(「㍉」「㌍」「㍻」等)、その他「彅」「髙」等の JIS X 0208 には入ってない文字が含まれています。
同じコードで異なる文字
上記のように基本的にはsjisに文字を追加したものがcp932なのですが、同じコードで異なる文字が割り当てられているものがあります。
「\」「~」「∥」「-」「¢」「£」「¬」の7文字です。
sjisだけまたはcp932だけを使用して処理している場合には、特に何も問題にならないのですが、charsetを変換する場合に問題になります。
sjisの「~」はcp932には存在しないし、cp932の「~」はsjisには存在しません。つまりsjisとcp932の相互に変換しようとしたときに問題になります。なおutf8mb4には両方とも存在するので、utf8mb4に変換することはできます。
どうしてこんなことになったのか興味がある人はWikipediaの日本語環境でのUnicodeの諸問題を見ましょう。
利用者定義領域(外字領域)
cp932にはsjisには無かった利用者定義領域(外字領域)が1880文字分あります(0xF040〜F9FC)。
MySQLではsjis,cp932のコードの範囲であれば文字が割り当てられていないコードの文字も使用することができますが、他のcharsetに変換することはできません。
Unicodeにも利用者定義領域と同様の私用領域が定められていて、このうちの1880文字がcp932の利用者定義領域とマッピングされています。
つまりcp932の0xF040〜F9FCの文字はutf8mb4には変換できますが、sjisの同じ領域の文字は変換することができません。
ということで、以上をMySQL上で確かめてみます。
まず sjis, cp932 の範囲の文字を含んだファイルを作ります。今回はRubyで次のように作りました。
((0x81..0x9f).to_a + (0xe0..0xfc).to_a).each do |c1| ((0x40..0x7e).to_a + (0x80..0xfc).to_a).each do |c2| code = format "%02X%02X", c1, c2 s = [c1, c2].pack('C*').b puts [code, s].join("\t") end end
これを sjis.rb として保存して、次のように実行すると sjis.txt が作られます。
% ruby sjis.rb > /tmp/sjis.txt
MySQLでsjisとcp932それぞれのテーブルを作り、sjis.txtをロードします。
mysql> create table sjis (code varchar(255) ascii primary key, c varchar(255)) charset sjis; mysql> create table cp932 (code varchar(255) ascii primary key, c varchar(255)) charset cp932; mysql> load data local infile '/tmp/sjis.txt' into table sjis charset sjis; mysql> load data local infile '/tmp/sjis.txt' into table cp932 charset cp932;
sjis と cp932 をそれぞれ utf8mb4 に変換して、違うコードになった文字を出力します。
mysql> select sjis.code, sjis.c sjis, cp932.c cp932 from sjis join cp932 using (code) where hex(convert(sjis.c using utf8mb4))!=hex(convert(cp932.c using utf8mb4)); +------+------+-------+ | code | sjis | cp932 | +------+------+-------+ | 815F | \ | \ | | 8160 | 〜 | ~ | | 8161 | ‖ | ∥ | | 817C | − | - | | 8191 | ¢ | ¢ | | 8192 | £ | £ | | 81CA | ¬ | ¬ | | 8740 | ? | ① | | 8741 | ? | ② | | 8742 | ? | ③ | | 8743 | ? | ④ | ...
出力結果は長いので https://gist.github.com/tmtm/c0d325ae74e43740f7ffa3d0dccb0bb4 に置いておきました。
815F,8160等のようにsjisとcp932の両方に文字があるものは、sjisとcp932の相互に変換できない文字です。utf8mb4には対応する文字が存在しているので、utf8mb4に変換することはできています。
?
となってるのは、utf8mb4に変換することができなかった文字です。コードに文字が割り当てられていないことを意味しています。