- MySQLのutf8問題は「文字コードの知識不足」ではなく設計の歴史
- MySQLのutf8は3バイトUTF-8
- 何が保存できないのか
- なぜこんな仕様になったのか
- スマートフォン時代と絵文字
- utf8mb4の登場
- なぜ何年も混乱が続いたのか
- よく起きる実害
- 安全な移行手順
- 注意点
- 誤解されがちなポイント
- まとめ:utf8問題は設定ではなく時代のズレ
MySQLのutf8問題は「文字コードの知識不足」ではなく設計の歴史
MySQLを扱っていると、必ず一度は遭遇します。
「utf8で作ったのに絵文字が入らない」
「文字化けしていないのにINSERTが失敗する」
この現象の原因は設定ミスではありません。結論から言うと、MySQLのutf8は本物のUTF-8ではないからです。
そしてその後に登場したutf8mb4が、本来のUTF-8に相当します。つまり、名前が逆です。これが長年混乱を生んできた理由です。
MySQLのutf8は3バイトUTF-8
通常、UTF-8は最大4バイトです。Unicodeのほぼすべての文字を表現できます。
しかしMySQLのutf8は違います。
- utf8 → 最大3バイト
- utf8mb4 → 最大4バイト
つまり、MySQLのutf8は「一部のUnicodeしか保存できないUTF-8」です。
何が保存できないのか
代表例が絵文字です。
INSERT INTO messages(text) VALUES('😊');
このとき発生するエラー:
Incorrect string value: '\xF0\x9F\x98\x8A'
これは文字化けではありません。保存拒否です。
データベースが「その文字は表現できない」と判断しています。
理由は単純で、😊は4バイトUnicodeだからです。
なぜこんな仕様になったのか
歴史的な事情です。
MySQLが広く使われ始めた2000年代初期、Unicodeはまだ普及途中でした。当時よく使われていた文字は次の範囲です。
- ASCII
- 日本語(JIS)
- 欧文拡張
これらは3バイト以内で表現できました。
つまり「4バイトUnicodeを扱う必要がほぼなかった」時代です。
そこでMySQLはメモリとストレージ効率を優先し、3バイトUTF-8をutf8として実装しました。
この判断自体は当時としては合理的でした。
問題は、その後スマートフォンとSNSが登場したことです。
スマートフォン時代と絵文字
iPhoneとAndroidが普及し、絵文字がテキストの一部になりました。
ここで4バイトUnicodeが日常的に使われ始めます。
しかしMySQLのutf8は3バイトのままです。
つまり、現代の文字文化とデータベース仕様が衝突しました。
その結果、現場では次のような不可解な現象が起きます。
- ブラウザでは正常表示
- PHPでも正常
- DB保存時だけエラー
ここでアプリのバグを疑いがちですが、原因は文字コードの上限です。
utf8mb4の登場
そこでMySQL5.5.3でutf8mb4が追加されました。
これが「本来のUTF-8」です。
utf8mb4に変更すると、絵文字も保存できます。
ALTER TABLE messages CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
しかし、ここから新しい問題が始まります。
なぜ何年も混乱が続いたのか
理由は「デフォルトがutf8のままだった」ことです。
多くの開発者はこう考えます。
- utf8 → 正しいUTF-8
- utf8mb4 → 特殊用途
実際は逆です。
utf8が特殊で、utf8mb4が標準です。
さらに混乱を増やした要因があります。
インデックス長制限
InnoDBには767バイト制限がありました。
utf8mb4は4バイト文字のため、VARCHAR(255)にインデックスを貼ると失敗します。
Specified key was too long; max key length is 767 bytes
このため多くのフレームワークがutf8を使い続けました。
つまり「正しい設定にすると動かない」時代があったのです。
よく起きる実害
この問題は単なる文字化けでは終わりません。
- 一部のユーザー名だけ登録できない
- LINE連携で落ちる
- 外部APIのデータ保存で例外
- 本番だけエラー
特に外部サービス連携で頻発します。
外部データは絵文字を含む可能性が高いからです。
安全な移行手順
いきなり変えると事故になります。順番が重要です。
1. DBのデフォルト変更
ALTER DATABASE app CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
2. テーブル変換
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4;
3. 接続文字コード
$pdo = new PDO($dsn, $user, $pass, [ PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4" ]);
注意点
インデックスがあるテーブルは失敗することがあります。
その場合はVARCHAR長の見直しが必要です。
例:
- VARCHAR(255) → VARCHAR(191)
これはutf8mb4の4バイトとInnoDB制限の関係です。
誤解されがちなポイント
SET NAMES utf8では直らない
通信エンコーディングと保存エンコーディングは別です。
カラムだけ変えても不十分
データベース、テーブル、接続の3箇所が一致する必要があります。
文字化けしない=安全ではない
保存拒否は表示確認では検出できません。
まとめ:utf8問題は設定ではなく時代のズレ
MySQLのutf8問題は設定ミスではありません。
過去の合理的な設計が、現代の文字利用に追いつかなくなっただけです。
utf8を使い続けると、普段は問題が出ません。しかし「特定のユーザーだけ失敗する」タイプの不具合を生みます。
それはバグではなく仕様の境界です。
新しいアプリケーションでは、最初からutf8mb4を前提に設計した方が結果的にトラブルが少なくなります。