俺の雑記帳

My random memorandumです。(つまり、個人的な備忘録であり、その点ご容赦を。)

MySQL移行で文字化け(「Warning (Code 1366) Incorrect string value 」や「Warning (Code 1265): Data truncated」が出る)

すでに移行して運用しているMySQLインスタンスで、一部のデータベースのみ文字化けしている現象に対処した。
 S-JISのデータベースがほとんどで、ある一つのデータベースのみEUC-JP(mysql表記ではujis)(さらに言えば、内1テーブルのみS-JIS)だった。
 移行した際は、S-JISのデータを入れるよう苦労したようだ。別担当者なので詳細は不明だが、ダンプしてそのファイルを修正して(ファイル中の「\」文字を取るとか??)やっと入れた。ただ、あるデータベースのみujisだったのに気付かず、それが文字化けしていた。
 これに以下の様に対応した。

【今回の私のケースの環境】
  MySQL 5.0.77
  CentOS 5.5
  移行前ダンプデータ:MySQL 3.23.58
  文字コード
    mysql> show variables like '%char%';
    +--------------------------+----------------------------+
    | Variable_name | Value |
    +--------------------------+----------------------------+
    | character_set_client | sjis |
    | character_set_connection | sjis |
    | character_set_database | utf8 |
    | character_set_filesystem | binary |
    | character_set_results | sjis |
    | character_set_server | sjis |
    | character_set_system | utf8 |
    | character_sets_dir | /usr/share/mysql/charsets/ |
    +--------------------------+----------------------------+
    8 rows in set (0.00 sec)

【対応した手順】
再移行に成功したが、結論として、手順の要点は以下。
(1) 移行前のダンプデータを確保。(移行後のデータ変更で重要なものがなかったため、移行前のデータから復元することで済んだ。)
(2) 必要な単位(データベース、テーブル、カラム)でデータと文字コードを設定。(最小ではカラム単位で文字コードを設定できる)。
  CREATE DATABASE hogehoge_transfer_test5 CHARACTER SET ujis;
  (今回は、DB単位で変えた。以下一例。)
  CREATE TABLE sjistable (
   id int(11) NOT NULL auto_increment,
   name text NOT NULL
   PRIMARY KEY (ID)
  ) ENGINE=MyISAM DEFAULT CHARSET=sjis;
(3) mysqlコマンドプロンプトで、「SET NAMES …」でデータと同じ文字コードを指定し、ダンプファイル投入。(2種類の文字コードが単一のダンプファイル中に混合している場合は、分割する。※秀丸などで分割する場合は、残したいデータの文字コードで開き(リネームして)保存する。さもないと文字化けしたデータを保存してしまう。)
  mysql> SOURCE dump_hogehoge.sjistable.sql;
  mysql> SET NAMES ujis;
  mysql> SHOW VARIABLES LIKE '%char%';
  +--------------------------+----------------------------+
  | Variable_name | Value |
  +--------------------------+----------------------------+
  | character_set_client | ujis |
  | character_set_connection | ujis |
  | character_set_database | ujis |
  | character_set_filesystem | binary |
  | character_set_results | ujis |
  | character_set_server | sjis |
  | character_set_system | utf8 |
  | character_sets_dir | /usr/share/mysql/charsets/ |
  +--------------------------+----------------------------+
  8 rows in set (0.00 sec)
  mysql> SOURCE dump_hogehoge_Modified2-2_NoSjistable.sql;
(4) アプリケーション側を変更し、「SET NAMES …;」をコネクションのたびに実行する。
(5) できたデータベースと動作を確認し、本番のデータベース名に変更する。
  『MySQLデータベース名の変更』
    http://d.hatena.ne.jp/foldrr/20091008/p1

※なお、上記(3)により、phpMyAdminではどの文字コードのデータでも正しく表示されるようになる。


【参考URLとコメント】

『日本語 INSERT 時に Warning (Code 1366) Incorrect string value ___ が出る問題 - 湖底概念++』
  http://d.hatena.ne.jp/lpm11/20070616/1181971116
このエラーが出たレコードの該当カラムのデータは、文字が正しく表示されない。
ただし、データの多くは、取り出し方を変更すれば、表示できる。(一部不能。以降で詳述(「Warning (Code 1265): Data truncated」となる関連))

『あんじーのテクニカルブログ 検索結果が正しくない』
  http://blog.development-network.net/ung/2008/02/post_179.html
基本的には、ここにあるように、データベースのデフォルト文字コードを変更(&データを入れなおす)すればよい。
 ここでは、現状のデータを入れなおす対応で完了しているが、データが格納できずエラーとなっていたデータもあるはず。(以降で詳述。(「Warning (Code 1265): Data truncated」となる関連))

MySQL文字コード設定を後から変えるテスト 民芸的プログラミング』
  http://kzworks.at.webry.info/200903/article_15.html
ここにある例では、後から文字コードを変えてもダメ。でもいつでもできないわけではない。それは、このブログ自身に記載があるように「Warning (Code 1265): Data truncated for column 'name' at row 1」が出てしまっているから。(latin1でなくダブルバイト文字コードの設定だったら一部は移行できたはず。その場合、既述の「Warning (Code 1366) Incorrect string value 」と同時に出るようだ。私の今回のケースでは、「Warning (Code 1366) Incorrect string value 」のみ出る場合が大多数で、文字によってまれに同時に「Warning (Code 1265): Data truncated」が出る。)
「Warning (Code 1265): Data truncatedが出たレコードは消失してしまっているが、「Warning (Code 1366) Incorrect string value 」のみのデータでは、後でもデータの取り出し方を変えれば復活できるはず。

ちなみに、mysqlコマンドプロンプト(コンソールでクエリ発行)の際、warningの内容を表示するには「\W」、
  mysql> \W
mysqlコマンドでファイルからクエリ発行等の際は、「--show-warnings」
  mysql -D hogehoge_transfer_test3 -u myAdmin -p --default-character-set=binary --show-warnings < dump_hogehoge_Modified
でできる。
なお、上記「--default-character-set=binary < dump_hogehoge_Modified」とmysqlプロンプト内での
  mysql> SET NAMES binary;
  mysql> SOURCE dump_hogehoge_Modified;
は同一のはずだが、私が「動作が違う」と結論づけたケースがあった。詳細は忘れた。


『日本MySQLユーザ会/FAQ/クライアントとサーバーのキャラクターセットの確認』
  http://www.mysql.gr.jp/frame/modules/bwiki/index.php?FAQ#f1b67614
あんじーのページで参考にしたとされるページ。
ここでは、「サーバーとクライアントのキャラクターセットが同じ場合は、文字の自動変換は起きず、文字化けは発生しない。」とあるが、私なりに解釈すると以下。
 mysqlコマンド等でのデータ挿入時は、SHOW VARIABLES LIKE '%char%';設定のクライアント(character_set_client/_connection/_resultのいずれか、もしくは複数の設定) と各オブジェクト(データベース※、テーブル、カラム)の定義 が同じであれば、正しく動作する。
 アプリケーションでの読み出しも、同様ではないだろうか。そのため、「SET NAMES …;」をコネクションのたびに実行しなければならないと考えると筋が通るのではないか。


【バイナリについて】
●DBの設定をバイナリする手も途中まで行ったが、止めた。何とか稼動はするかもしれないが、テキストや文字のデータ型が、バイナリ関連の型になり、動作がおかしくなる恐れがあった(PhpMyAdminではデータが見れなくなった。当社制作のアプリケーションではとりあえず文字化けせずに表示はされていたが、それ以上の検証はしなかった。)
DBをバイナリにしたり、以下の様にテーブル単位でバイナリにしたりする。
  CREATE TABLE sjistable (
   id int(11) NOT NULL auto_increment,
   name text NOT NULL
   PRIMARY KEY (ID)
  ) ENGINE=MyISAM DEFAULT CHARSET=binary;
上記のSQLでテーブルはできるが、データ型がtext⇒blob、varchar(128)⇒varbinary(128) などと勝手に変化してしまう。
 まず通常の文字型で作って、CHARSETを後で変更しても、同様に変化してしまう。
●「SET NAMES binary;」としても、DBオブジェクト側のデータ型が同じ(binary)でないと、自動文字変換しようとしてWarningがでる。