UTF-8 und Unicode Warum dieses Emoji Ihre Datenbank zerstört
Ihre App hat ein Emoji eingefügt und MySQL hat „Incorrect string value“ geworfen. Hier erfahren Sie, warum – Codepunkte vs. Bytes, das Lügen von MySQL utf8 vs. utf8mb4, JavaScript-Surrogate-Paare und wie man es tatsächlich behebt.
Ihre Anwendung funktionierte bisher gut. Dann gab ein Benutzer ein Emoji in ein Textfeld ein, und MySQL warf Incorrect string value: '\xF0\x9F\x98\x80' for column 'bio'. Oder vielleicht verschwand das Emoji schweigend. Oder die gesamte INSERT-Anweisung scheiterte und Sie verloren Daten. Alles wegen eines vier-Byte-Zeichens, das Ihre Datenbankspalte nicht erwartete.
Das ist kein MySQL-Problem oder ein PHP-Fehler. Es ist eine Konsequenz davon, wie Unicode → UTF-8-Encodierung tatsächlich funktioniert, und sobald Sie das verstehen, werden Sie es nie mehr überraschen.
Code Points im Vergleich zu Bytes: der tatsächliche Unterschied
Unicode weist jedem Zeichen einen Code Point — eine Zahl — zu. Der Buchstabe A ist U+0041. Das Euro-Symbol ist U+20AC. Das 😀-Emoji ist U+1F600. Das ist die abstrakte Identität des Zeichens.
UTF-8 ist ein Codierung — eine Methode, um Code Points als Bytes zu speichern. Der Trick besteht darin, dass UTF-8 variable Breite hat: es verwendet 1 bis 4 Bytes, abhängig vom Code Point-Wert. So bleibt es mit ASCII kompatibel (alle ASCII-Zeichen sind 1 Byte in UTF-8) und kann gleichzeitig jedes existierende Zeichen codieren.
Die Codierungsregeln:
- U+0000 bis U+007F (ASCII) → 1 Byte
- U+0080 bis U+07FF (erweiterte Latein-Schreibweise, Arabisch, Hebräisch usw.) → 2 Bytes
- U+0800 bis U+FFFF (die meisten CJK-Zeichen, Punktzeichen, Symbole) → 3 Bytes
- U+10000 bis U+10FFFF (Emojis, seltene Schriften, mathematische Symbole) → 4 Bytes
Deswegen benötigt das 😀-Emoji (U+1F600) 4 Bytes: Sein Code Point liegt über U+FFFF.
Größe von UTF-8-Bytes: eine Referenztabelle
Hier ist, was gängige Zeichen tatsächlich in Bytes kosten:
| Zeichen | Beschreibung | Unicode-Codepunkt | UTF-8-Bytes (Hex) | Byte-Anzahl |
|---|---|---|---|---|
| A | Lateinische Großbuchstabe A | U+0041 | 41 | 1 |
| é | Lateinische e mit Akzent | U+00E9 | C3 A9 | 2 |
| € | Euro-Symbol | U+20AC | E2 82 AC | 3 |
| 中 | Chinesische Zeichen „Mittlere“ | U+4E2D | E4 B8 AD | 3 |
| 😀 | Grinsendes Gesicht-Emoji | U+1F600 | F0 9F 98 80 | 4 |
| 🔥 | Feuer-Emoji | U+1F525 | F0 9F 94 A5 | 4 |
| 𝕳 | Mathematisches Fraktur-H | U+1D573 | F0 9D 95 B3 | 4 |
Um dies selbst zu überprüfen, verwenden Sie die Zeichenlängenrechner — es zeigt sowohl die Zeichenanzahl als auch die Bytes-Anzahl für jeden Text, den Sie einfügen. Fügen Sie 😀 ein und Sie sehen 1 Zeichen, aber 4 Bytes.
Das MySQL-utf8-Mythos
Hier wird Entwicklerinnen und Entwickler verbrannt. MySQL besitzt eine Zeichensatz-Bezeichnung namens utf8. Klingt logisch. Es ist falsch — MySQLs utf8 unterstützt nur bis zu 3-Byte-Sequenzen. Emojis (4 Bytes) werden nicht unterstützt.
Der tatsächliche vollständige UTF-8-Zeichensatz in MySQL ist utf8mb4 (eingeführt in MySQL 5.5.3, veröffentlicht 2010). Wenn Ihre Spalte utf8 verwendet und jemand ein Emoji einfügt, wird MySQL entweder die Daten schweigend abgeschnitten oder wirft:
Incorrect string value: '\xF0\x9F\x98\x80' for column 'bio' at row 1
Die Lösung:
-- 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;
Auch aktualisieren Sie Ihre Anwendungskonfiguration für die Datenbankverbindung. Bei MySQL PDO:
$pdo = new PDO($dsn, $user, $pass, [
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4"
]);
Der VARCHAR(255)-Falle
VARCHAR(255) in MySQL bedeutet 255 Zeichen, nicht 255 Bytes — aber die Speicherbegrenzung für eine einzelne Zeile wird in Bytes berechnet. Mit utf8mb4kann jedes Zeichen bis zu 4 Bytes benötigen, wodurch eine VARCHAR(255) Spalte bis zu 1.020 Bytes reserviert. Dies spielt eine Rolle, wenn Sie InnoDBs Standardgrenze für Präfix-Indizes (767 Bytes) für varchar-Spalten verwenden:
-- 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
JavaScript und das Problem der Surrogate-Paare
JavaScript verwendet intern UTF-16, nicht UTF-8. Und UTF-16 hat seine eigene mehrfache Codierung für Code Points über U+FFFF: Surrogate-Paare — zwei 16-Bit-Codes, die gemeinsam ein Zeichen darstellen.
Das bedeutet, String.length in JavaScript zählt die Anzahl der UTF-16-Codes, nicht Zeichen:
'😀'.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)
Für String-Operationen, die zeichenbewusst sein müssen, verwenden Sie den Spread-Operator oder 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)
Das Beispiel mit dem Familien-Emoji ist wertvoll, um innezuhalten. 👨👩👧👦 ist vier Emojis, verbunden durch Zero Width Joiners (U+200D). Ein naives .length gibt Ihnen 11. Die tatsächlichen Grapheme-Cluster: 1. Dies spielt eine Rolle, wenn Sie Zeichenbegrenzungen implementieren — eine Grenze basierend auf String.length verhält sich unerwartet, wenn Benutzer Emoji-Folgen eingeben.
Wie man die Codierung praktisch überprüft
Python
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() in PHP zählt Bytes, nicht Zeichen. Dies fängt PHP-Entwickler ständig ein, wenn sie mit mehrbyte-Zeichen arbeiten — eine 10-Emoji-Zeichenkette wird eine Länge von 40 anzeigen. Verwenden Sie mb_strlen() bei Bedarf der Zeichenanzahl.
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
Schnelle Überprüfung
Wenn Sie die Bytes- und Zeichenanzahl für beliebige Texte ohne Code sehen möchten, kann die Zeichenlängenrechner das sofort lösen — fügen Sie beliebigen Text ein und es zeigt Zeichenanzahl, Wortanzahl und Bytesanzahl nebeneinander an.
Die Codierungsfehler-Checkliste
- MySQL-Zeichensatz: Ist es
utf8mb4sein, nichtutf8? Prüfen Sie mitSHOW CREATE TABLE. - MySQL-Verbindung: Ist Ihre Anwendung
SET NAMES utf8mb4? Prüfen Sie Ihre DSN oder Verbindungskonfiguration. - PHP strlen vs mb_strlen: Verwenden Sie Sie Byte-Zählfunktionen, wo Sie Zeichenanzahlen benötigen?
- JavaScript .length: Zählen Sie Sie Code-Einheiten, wo Sie Grapheme-Cluster benötigen?
- HTTP-Header: Sendet Ihre Antwort
Content-Type: text/html; charset=utf-8? - Datei-Codierung: Sind Ihre Quelldateien und SQL-Exporte als UTF-8 ohne BOM gespeichert?
Erweiterungen installieren
IO-Tools zu Ihrem Lieblingsbrowser hinzufügen für sofortigen Zugriff und schnellere Suche
恵 Die Anzeigetafel ist eingetroffen!
Anzeigetafel ist eine unterhaltsame Möglichkeit, Ihre Spiele zu verfolgen. Alle Daten werden in Ihrem Browser gespeichert. Weitere Funktionen folgen in Kürze!
Unverzichtbare Tools
Alle Neuheiten
AlleAktualisieren: Unser neuestes Werkzeug wurde am Juni 19, 2026 hinzugefügt
