【MySQL】文字化けトラブルシューティング

MySQL

こんにちは、かつコーチです。

「絵文字を保存しようとしたらエラーになった」

「日本語のデータが???縺薙s縺ォ縺。縺ッのような文字化けで表示される」

MySQLを触っていると、一度は文字コードのトラブルに遭遇するのではないでしょうか。

文字コードの設定は、アーキテクチャ編の「文字コード・照合順序」の記事で基本を扱いましたが、この記事では実際に文字化けが起きたときの調査・対処の手順にフォーカスして解説します。

原因を切り分ける手順さえ押さえておけば、文字化けは意外とパターンが少なく、落ち着いて対処できるトラブルです。

文字化けが起きる典型的なパターン

絵文字保存時のエラー

Twitterのようなサービスを模した投稿機能を作っていて、次のエラーに遭遇したことがあります。

ERROR 1366 (HY000): Incorrect string value: '\xF0\x9F\x98\x80...' for column 'body' at row 1

これは、絵文字(😀など)をutf8(MySQLのutf8は実質3バイトまでしか扱えない不完全な実装)のカラムに保存しようとしたときに発生するエラーです。

\xF0\x9F\x98\x80は絵文字のバイト列を表しており、4バイト分のデータであることが読み取れます。

MySQLのutf8文字コードは、実は本家のUTF-8とは異なり最大3バイトまでしか扱えないという歴史的な制限があります。

絵文字や一部の特殊な漢字は4バイトを必要とするため、utf8のカラムには保存できずこのエラーになります。

文字化けした状態で表示される

エラーにはならないものの、保存したはずの日本語が化けて表示されるパターンもあります。

SELECT * FROM users;
+----+------------------------------+
| id | name                         |
+----+------------------------------+
|  1 | ??????                       |
+----+------------------------------+

あるいは、次のような文字化けパターンもあります。

縺薙s縺ォ縺。縺ッ

前者(??になるパターン)は文字コードの変換に失敗し情報が失われている状態、後者(意味不明な文字列になるパターン)は文字コードの解釈がズレているだけで情報自体は失われていない状態です。

この違いを見極めることが、原因調査の最初のステップになります。

原因を調査する手順

ステップ1:接続時の文字コードを確認する

まず疑うべきは、クライアントとサーバー間の接続時に使われている文字コードです。

SHOW VARIABLES LIKE 'character_set%';
+--------------------------+--------------------------------------------------+
| Variable_name            | Value                                             |
+--------------------------+--------------------------------------------------+
| character_set_client     | utf8mb4                                           |
| character_set_connection | utf8mb4                                           |
| character_set_database   | utf8mb4                                           |
| character_set_results    | utf8mb4                                           |
| character_set_server     | utf8mb4                                           |
+--------------------------+--------------------------------------------------+

character_set_clientcharacter_set_connectioncharacter_set_resultsのいずれかだけが他と異なっている場合、その部分で変換ミスが起きている可能性が高いです。

「縺薙s縺ォ縺。縺ッ」のような化け方は、典型的なUTF-8のデータをShift_JISとして誤って解釈したことで起きるパターンです。

ステップ2:カラムの文字コードを確認する

接続時の文字コードが正しくても、テーブル・カラム自体の文字コードが古い設定のままになっているケースがあります。

SHOW CREATE TABLE users;
CREATE TABLE `users` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

このように、テーブル全体はutf8mb4なのに特定のカラムだけ古いutf8のままになっている、というケースは意外とよくあります。

過去に追加した古いカラムがそのまま残っていたり、他の環境からダンプをインポートした際に設定が引き継がれていたりすることが原因です。

文字化けの直し方

接続文字コードを揃える

アプリ側の接続設定で、明示的にutf8mb4を指定します。

// ❌ Before:文字コードを明示していない
$pdo = new PDO('mysql:host=localhost;dbname=myapp', $user, $pass);
// ✅ After:charsetを明示的に指定する
$pdo = new PDO('mysql:host=localhost;dbname=myapp;charset=utf8mb4', $user, $pass);

charset=utf8mb4を接続文字列に明示するだけで、多くの文字化けトラブルは未然に防げます。

カラムの文字コードを変換する

既にutf8で作られてしまったカラムをutf8mb4に変換するには、ALTER TABLEを使います。

-- テーブル全体の文字コードとカラムをまとめて変換する
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;

注意点として、このALTER TABLE内部的にはデータを一度読み込んで書き直す処理のため、レコード数の多いテーブルでは時間がかかり、実行中はテーブルがロックされる場合があります。

本番環境で実行する際は、必ずメンテナンス時間を確保するか、深夜帯などアクセスの少ない時間帯に実行するようにしてください。

筆者が過去に数十万件規模のテーブルでこの変換を実行した際は、想定より時間がかかり、事前に検証環境で所要時間を計測しておくべきだったと反省した経験があります。

すでに化けたデータの復旧は難しい

ここで重要な注意点があります。

??のように情報そのものが失われてしまった文字化け(先述の「変換に失敗し情報が失われている」パターン)は、後から元の文字列に復元することはできません。

一方、「縺薙s縺ォ縺。縺ッ」のように文字コードの解釈がズレているだけのパターンは、正しい文字コードで再解釈することで復元できる可能性があります。

-- 誤った文字コード解釈を修正して復元を試みる例(環境により調整が必要)
SELECT CONVERT(BINARY CONVERT(name USING latin1) USING utf8mb4) FROM users;

ただし、これは元データがどの文字コードで書き込まれたかを正確に把握できている場合に限られる応急処置です。

文字化けは「起きてから直す」より「起きないように設定する」方が圧倒的にコストが低いため、新規プロジェクトでは最初からutf8mb4で統一しておくことを強くおすすめします。

つまずきやすいポイント・エラー対処

mysqldump時の文字化け

mysqldumpでバックアップを取る際、文字コードの指定を忘れると出力ファイルが文字化けすることがあります。

# ❌ Before:文字コード未指定でダンプを取る
mysqldump -u root -p myapp > backup.sql
# ✅ After:デフォルト文字コードを明示してダンプを取る
mysqldump -u root -p --default-character-set=utf8mb4 myapp > backup.sql

バックアップ・リストア時の文字化けは、mysqldump編の記事でも触れていますが、--default-character-setオプションの明示は特に見落としやすいポイントです。

応用・一歩先の使い方(中級読者向けブリッジ)

utf8mb4_general_ciとutf8mb4_0900_ai_ciの違い

MySQL 8.0からは、utf8mb4照合順序(文字列の比較・ソート方法のルール)のデフォルトがutf8mb4_general_ciからutf8mb4_0900_ai_ciに変わりました。

古いバージョンからアップグレードしたプロジェクトでは、照合順序が混在していることでソート順の意図しない挙動や、テーブル結合時のエラーが起きることがあります。

ERROR 1267 (HY000): Illegal mix of collations (utf8mb4_general_ci,IMPLICIT) and (utf8mb4_0900_ai_ci,IMPLICIT) for operation '='

このエラーが出た場合は、比較対象のカラム同士で照合順序を揃える必要があります。

新規プロジェクトでは特別な理由がない限り、MySQL 8.0のデフォルトであるutf8mb4_0900_ai_ciに統一しておくのが無難です。

まとめ

この記事のポイント

  • 絵文字の保存エラーは、utf8(3バイト制限)とutf8mb4(4バイト対応)の違いが原因
  • 文字化けの原因調査は「接続時の文字コード」と「カラムの文字コード」の両方を確認する
  • ??になる文字化けは復元不可能、文字コード解釈のズレによる文字化けは復元できる場合がある
  • mysqldump実行時は--default-character-setオプションを忘れない
  • 照合順序の混在エラーにも注意し、新規プロジェクトはutf8mb4_0900_ai_ciに統一する

次に読むべき記事

  • よくあるMySQLエラーまとめ(Access denied等)
  • 文字コード・照合順序(utf8mb4)でハマらないために

タグ: MySQL, 中級者向け, エラー解決

タイトルとURLをコピーしました