MySQLのsjisとcp932の違い

今さらですけど、自分でもちゃんと把握してなかったので調べてみました。

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に変換することができなかった文字です。コードに文字が割り当てられていないことを意味しています。