MySQLのutf8とutf8mb4はなぜ混乱が続いたのか

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を前提に設計した方が結果的にトラブルが少なくなります。