広告が嫌いですか? 行く 広告なし 今日

UTF-8 と Unicode そのエモジがデータベースを壊した理由

更新日

あなたのアプリケーションがエモジを挿入し、MySQLが不正な文字列値を投げました。その理由は——コードポイントとバイト、MySQLのutf8とutf8mb4の誤解、JavaScriptのサブリプレートペア、そして実際に解決する方法です。

あなたのアプリがエモジを挿入したところ、MySQLが不正な文字値を投げました。その理由は、コードポイントとバイトの違い、MySQLのutf8とutf8mb4の誤り、JavaScriptのサルベージペア、そして実際にそれを解決する方法です。

あなたのアプリはうまく動いていた。しかし、ユーザーがテキストフィールドにエモジを入力したところ、MySQLが Incorrect string value: '\xF0\x9F\x98\x80' for column 'bio'を投げた。あるいは、エモジが静かに消えた。あるいは、INSERTが完全に失敗し、データが失われた。すべて、データベースカラムが期待していない4バイトの文字によるものだった。

これはMySQLの特徴やPHPのバグではなく、Unicode → UTF-8エンコーディングが実際にどのように機能するかの結果であり、それを理解すれば、もう驚かされることはない。

コードポイントとバイト:実際の違い

Unicodeはすべての文字にコードポイントを割り当てます — 数値です。Aの文字はU+0041。ユーロ記号はU+20AC。😀エモジはU+1F600。これは文字の抽象的な識別です。 UTF-8は

— コードポイントをバイトとして保存する方法です。ポイントは、UTF-8は可変幅であるため、コードポイントの値に応じて1〜4バイトを使用します。これにより、ASCIIと後方互換性を保ちつつ、存在するすべての文字をエンコードできます。 エンコーディング エンコーディングルール:

U+0000 から U+007F(ASCII)→ 1バイト

  • U+0080 から U+07FF(ラテン拡張、アラビア語、ヘブライ語など)→ 2バイト
  • U+0800 から U+FFFF(多くのCJK文字、記号、記号)→ 3バイト
  • U+10000 から U+10FFFF(エモジ、稀な文字、数学記号)→ 4バイト
  • これが、😀エモジ(U+1F600)が4バイトを必要とする理由です。そのコードポイントはU+FFFFを超えるからです。

UTF-8のバイトサイズ:参照表

ここに一般的な文字が実際に消費するバイト数を示します:

UTF-8 バイト(16進数)

文字説明ユニコードコードポイントラテン大文字Aバイト数
U+0041é411
ラテンeの急傾斜U+00E9C3 A9ユーロ記号2
U+20ACE2 82 AC3
中国語の「中」U+4E2DE4 B8 AD😀3
笑顔の顔エモジU+1F600F0 9F 98 80火エモジ4
🔥U+1F525F0 9F 94 A5𝕳4
数学のフランクルHU+1D573F0 9D 95 B3これを自分で確認するには、4

— あなたが貼り付けるテキストに対して、文字数とバイト数を表示します。を貼り付けて、1文字ですが4バイトであることがわかります。 文字列長計算機 MySQLのutf8の誤り 😀 開発者が傷つく場所です。MySQLには

という文字コードがあります。名前は正しいように見えますが、間違っています — MySQLの

は最大3バイトのシーケンスしかサポートしていません。エモジ(4バイト)はサポートされません。 utf8実際のフルUTF-8文字コードは utf8 (MySQL 5.5.3で導入、2010年にリリース)です。もしカラムが

を使用していて、エモジを挿入すると、MySQLはデータを静かにトリムするか、または utf8mb4 を投げます。また、アプリケーションのデータベース接続設定を更新してください。MySQL PDOの場合: utf8 VARCHAR(255)の罠

Incorrect string value: '\xF0\x9F\x98\x80' for column 'bio' at row 1

(バイト)を使用します。Laravelでは

-- Convert the table
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- Or for a specific column
ALTER TABLE users MODIFY bio TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- And set your connection charset
SET NAMES utf8mb4;

MySQLでは255

$pdo = new PDO($dsn, $user, $pass, [
    PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4"
]);

文字

VARCHAR(255) 、ではなく255バイト — しかし、1行のストレージ制限はバイト単位で計算されます。 の場合、各文字は最大4バイトを消費し、カラムは最大1,020バイトを確保します。これは、InnoDBのデフォルトのプレフィックスインデックス制限(767バイト)を適用するvarcharカラムに影響します。 utf8mb4JavaScriptとサルベージペアの問題 VARCHAR(255) JavaScriptは内部でUTF-16を使用しており、UTF-8とは異なります。UTF-16はU+FFFFを超えるコードポイントに対して、独自のマルチユニットエンコーディングを持っています:

-- This fails on older MySQL with default innodb_large_prefix=OFF
CREATE INDEX idx_email ON users (email);  -- email is VARCHAR(255) utf8mb4

-- Fix: use a prefix index
CREATE INDEX idx_email ON users (email(191));  -- 191 * 4 = 764 bytes, under 767

-- Or enable large prefixes (MySQL 5.7+, on by default in 8.0)
-- Set innodb_large_prefix = ON in my.cnf

サルベージペア

— 2つの16ビットコードユニットが1つの文字を表します。 これにより、 JavaScriptはUTF-16コードユニットを数え、文字ではなくコードユニットを数えます:

文字操作に必要な場合は、スプレッドオペレーターまたは String.length を使用してください。

'😀'.length        // → 2 (two UTF-16 surrogate code units)
[...'😀'].length   // → 1 (spread operator uses Unicode code points)

// Checking the character at index 0
'😀'[0]            // → '\uD83D' (the high surrogate, not the emoji)
'😀'.codePointAt(0) // → 128512 (0x1F600, correct)

家族エモジの例はここで一時的に止めてください。 Intl.Segmenter:

// Count actual grapheme clusters
const segmenter = new Intl.Segmenter();
const chars = [...segmenter.segment('👨‍👩‍👧‍👦')];
chars.length     // → 1 (family emoji is one grapheme cluster)
'👨‍👩‍👧‍👦'.length  // → 11 (UTF-16 code units)

はゼロ幅ジョインャー(U+200D)で結合された4つのエモジです。単純な 👨‍👩‍👧‍👦 は11を返します。実際のグラフェムクラスタ:1です。これは、文字制限を実装する場合に、ユーザーがエモジシーケンスを入力するときに、制限が不正確になることを意味します。 .length エンコーディングの確認方法 String.length PHPはバイトを数え、文字を数えません。これは、マルチバイト文字を扱うPHP開発者が常に遭遇する問題です — 10エモジの文字列は長さ40を報告します。文字数を気にする場合は、

を使用してください。

パイソン

s = '😀'
print(len(s))                    # 1 (Python 3 counts code points)
print(len(s.encode('utf-8')))    # 4 bytes
print(s.encode('utf-8').hex())   # f09f9880

PHP

$s = '😀';
echo strlen($s);          // 4 (bytes, not characters)
echo mb_strlen($s);       // 1 (characters)
echo mb_strlen($s, '8bit'); // 4 (bytes, explicit)

strlen() 簡易チェック mb_strlen() 任意のテキストのバイト数と文字数を確認したい場合、コードを書かずとも、

MySQL

-- Check charset of a table
SHOW CREATE TABLE users\G

-- Check charset of a specific column
SELECT CHARACTER_SET_NAME, COLLATION_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'users' AND COLUMN_NAME = 'bio';

-- Character count vs byte count
SELECT CHAR_LENGTH('😀'), LENGTH('😀');
-- → 1, 4

は即座に処理します — どんなテキストでも貼り付けて、文字数、語数、バイト数を並列に表示します。

エンコーディングのバグチェックリスト 文字列長計算機 MySQL文字コード:

それは

  • ですか?確認方法は MySQL接続: utf8mb4、ではなく utf8アプリケーションが SHOW CREATE TABLE.
  • を送信していますか?DSNまたは接続設定を確認してください。 PHP strlen と mb_strlen: SET NAMES utf8mb4バイト数を数える関数を使用している場合、必要に応じて文字数を数えていませんか?
  • JavaScript .length: コードユニットを数えている場合、必要に応じてグラフェムクラスタを数えていませんか?
  • HTTPヘッダー: レスポンスが
  • を送信していますか? ファイルエンコーディング: Content-Type: text/html; charset=utf-8?
  • ソースファイルやSQLダンプがUTF-8 without BOMで保存されていますか? UTF-8 と Unicode:そのエモジがデータベースを壊した理由 2
広告なしで楽しみたいですか? 今すぐ広告なしで

拡張機能をインストールする

お気に入りのブラウザにIOツールを追加して、すぐにアクセスし、検索を高速化します。

に追加 Chrome拡張機能 に追加 エッジ拡張 に追加 Firefox 拡張機能 に追加 Opera 拡張機能

スコアボードが到着しました!

スコアボード ゲームを追跡する楽しい方法です。すべてのデータはブラウザに保存されます。さらに多くの機能がまもなく登場します!

ニュースコーナー 技術ハイライト付き

参加する

価値ある無料ツールの提供を継続するためにご協力ください

コーヒーを買って