Zeichensatz in der Kette

Wenn aus ü plötzlich ü wird

Der entscheidende Unterschied ist, ob nur die Anzeige verbogen ist oder schon die gespeicherten Bytes. Ein einziger HEX-Aufruf beantwortet das, und davon hängt der ganze weitere Weg ab.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Seit 1997 am Markt Kleine Gruppen Präsenz und Live-Online Zertifizierte Trainer
Worum es geht

Zwischen falscher Anzeige und beschädigten Daten liegen Welten

Der Zeichensalat sieht in beiden Fällen gleich aus. Im ersten Fall stehen die Bytes richtig in der Tabelle und werden nur beim Herausgeben falsch ausgelegt, dann genügt eine korrekte Verbindungseinstellung und alles ist wieder in Ordnung. Im zweiten Fall sind die Bytes selbst schon verbogen, und dann hilft keine Einstellung mehr, sondern nur eine Umrechnung des Bestands.

Teuer wird die Verwechslung, weil die naheliegende Reparatur den zweiten Fall erzeugt. Jemand stellt fest, dass die Tabelle als latin1 deklariert ist, führt ALTER TABLE mit CONVERT TO CHARACTER SET utf8mb4 aus und verdoppelt damit die Kodierung: Die Bytes waren bereits UTF-8, werden aber als latin1 gelesen und ein zweites Mal umgewandelt. Aus ü wird dann nicht wieder ü, sondern ü in verschärfter Form, und der Weg zurück ist deutlich mühsamer.

Der dritte und schlimmste Zustand ist das Fragezeichen. Wenn ein Wert in einen Zeichensatz übertragen wurde, der das Zeichen nicht darstellen kann, ersetzt MySQL es durch ein Fragezeichen. Aus dieser Ersetzung führt kein Weg zurück, weil die Information nicht mehr vorhanden ist. Dann bleibt nur die Sicherung.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Symptom, Ursache, Lösung

Symptom, Ursache, Lösung

Symptom

In der Webseite steht ü, in der Datenbankverwaltung sieht derselbe Wert richtig aus.

Ursache

Die gespeicherten Bytes sind korrektes UTF-8, HEX zeigt C3BC. Die Anwendung baut ihre Verbindung aber mit latin1 auf und legt die zwei Byte deshalb als zwei getrennte Zeichen aus. Die Daten sind unversehrt.

Lösung

Den Zeichensatz in der Verbindungsangabe des Treibers auf utf8mb4 setzen, bei PDO über charset im Verbindungsstring, bei anderen Treibern über die entsprechende Option. SET NAMES utf8mb4 nach dem Verbindungsaufbau wirkt genauso, wird aber leicht in einem Codepfad vergessen.

Symptom

Schon in der Tabelle steht ü, obwohl das Altsystem die Umlaute richtig angezeigt hat.

Ursache

Die Anwendung hat UTF-8-Bytes über eine als latin1 deklarierte Verbindung in eine latin1-Spalte geschrieben. MySQL hat nichts umgerechnet, deshalb liegen dort korrekte UTF-8-Bytes unter falscher Deklaration. HEX zeigt C3BC in einer latin1-Spalte.

Lösung

Nicht mit CONVERT TO umstellen, das würde ein zweites Mal umrechnen. Stattdessen die Spalte über den Umweg des Binärtyps neu auslegen: ALTER TABLE t MODIFY spalte VARBINARY(255), danach ALTER TABLE t MODIFY spalte VARCHAR(255) CHARACTER SET utf8mb4. Vorher auf einer Kopie prüfen.

Symptom

Nach der Umstellung auf utf8mb4 steht in den Daten ü statt ü.

Ursache

Doppelte Kodierung. Die Bytes waren bereits UTF-8, wurden aber als latin1 gelesen und dabei noch einmal nach UTF-8 umgerechnet. HEX zeigt vier Byte, C383C2BC, wo zwei stehen sollten.

Lösung

Den Vorgang rückgängig machen, indem der Wert erst als latin1 ausgelesen, dann binär genommen und wieder als utf8mb4 gelesen wird: UPDATE t SET spalte = CONVERT(BINARY(CONVERT(spalte USING latin1)) USING utf8mb4). Das läuft zuerst auf einer Kopie und mit einer Zählung, wie viele Zeilen betroffen sind.

Symptom

Statt des Umlauts steht ein Fragezeichen in der Spalte.

Ursache

Der Wert wurde in einen Zeichensatz übertragen, der ihn nicht darstellen kann, etwa nach ascii oder in eine latin1-Spalte mit einem Zeichen außerhalb dieses Bereichs. MySQL setzt dann ein Fragezeichen ein.

Lösung

Hier hilft nur die Sicherung, weil die ursprüngliche Information nicht mehr in der Zeile steht. Anschließend die gesamte Kette auf utf8mb4 stellen, damit derselbe Import nicht wieder dasselbe Ergebnis liefert.

Symptom

Umlaute funktionieren, aber ein Emoji löst Fehler 1366 aus.

Ursache

Die Spalte liegt in utf8mb3, also dem Alias utf8, und der kann höchstens drei Byte je Zeichen. Zeichen außerhalb der Basisebene brauchen vier Byte und passen nicht hinein.

Lösung

Die Tabelle mit ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci umstellen. Prüf dabei die Indizes: Ein Index über VARCHAR(255) braucht mit vier Byte je Zeichen 1.020 Byte, was mit dem Zeilenformat DYNAMIC passt, mit älteren Zeilenformaten aber an der Grenze von 767 Byte scheitert.

Fünf Ebenen, an denen ein Zeichensatz festgelegt wird

  1. 01 Der Server hat eine Voreinstellung, die nur für neue Datenbanken gilt.
  2. 02 Die Datenbank vererbt ihre Einstellung an neu angelegte Tabellen.
  3. 03 Die Tabelle vererbt sie an neue Spalten, ändert bestehende aber nicht.
  4. 04 Die Spalte selbst entscheidet, wie ihre Bytes ausgelegt werden.
  5. 05 Die Verbindung legt fest, wie Werte hinein- und hinausgehen.
Was du mitnimmst

Danach findest du die Stelle, an der es kippt, statt zu raten

Der Ablauf ist immer derselbe: erst feststellen, welche Bytes tatsächlich gespeichert sind, dann die vier Einstellungen der Kette vergleichen, und erst danach entscheiden, ob eine Verbindungseinstellung reicht oder der Bestand umgerechnet werden muss.

Mit HEX den tatsächlichen Zustand feststellen

Ein ü ist in UTF-8 die Bytefolge C3BC, in latin1 das einzelne Byte FC und bei doppelter Kodierung C383C2BC. SELECT HEX(spalte) beantwortet damit als einziger Test zuverlässig, ob die Daten oder nur die Anzeige betroffen sind.

Die vier Ebenen der Kette auseinanderhalten

Server, Datenbank, Tabelle und Spalte haben je eine Voreinstellung, und die Spalte gewinnt. Eine Tabelle kann längst auf utf8mb4 stehen, während einzelne Spalten noch latin1 sind, sichtbar in SHOW CREATE TABLE.

Die Verbindung als eigene Baustelle sehen

character_set_client, character_set_connection und character_set_results werden mit SET NAMES utf8mb4 gemeinsam gesetzt. In Anwendungen gehört das in die Verbindungsangabe des Treibers, nicht in eine Anweisung nach dem Verbindungsaufbau.

utf8 von utf8mb4 unterscheiden

utf8 ist in MySQL ein Alias für utf8mb3 und kann höchstens drei Byte je Zeichen. Emojis und einige seltene Schriftzeichen brauchen vier Byte und werden mit Fehler 1366 abgelehnt. utf8mb3 gilt als veraltet und wird künftig entfallen.

Beim Umstellen die richtige Anweisung wählen

ALTER TABLE mit CONVERT TO CHARACTER SET rechnet den Inhalt um, ALTER TABLE mit CHARACTER SET ändert nur die Voreinstellung für neue Spalten. Wer beide verwechselt, wundert sich später über eine Tabelle mit gemischten Zeichensätzen.

Falsch deklarierte Bytes ohne Umrechnung retten

Stehen korrekte UTF-8-Bytes in einer latin1-Spalte, ist der Umweg über VARBINARY der richtige: erst auf den Binärtyp ändern, dann auf VARCHAR mit utf8mb4. Dabei werden die Bytes neu ausgelegt statt umgerechnet.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt

utf8 ist in MySQL nicht UTF-8

Der Zeichensatz utf8 steht in MySQL für utf8mb3 und speichert höchstens drei Byte je Zeichen. Seit MySQL 8.0.28 ist utf8mb3 der eigentliche Name und utf8 nur noch der veraltete Alias dazu, davor war es umgekehrt. Damit deckt er die Basisebene von Unicode ab, also Umlaute, Akzente und die meisten europäischen Schriften, aber nicht Emojis, nicht mathematische Sonderzeichen und nicht einen Teil der asiatischen Schriftzeichen. Vollständiges UTF-8 heißt in MySQL utf8mb4.

Das Handbuch bezeichnet utf8mb3 inzwischen als veraltet und kündigt an, dass es in einer künftigen Hauptversion entfällt. Für neue Anwendungen ist utf8mb4 die einzig sinnvolle Wahl, und für bestehende ist die Umstellung eine Frage der Zeit, nicht des Ob.

Praktisch bedeutet das: Wenn in einer Spaltendefinition utf8 steht, ist das utf8mb3 und damit ein Zeichensatz mit einer Lücke. Der Fehler zeigt sich nicht bei Umlauten, sondern erst, wenn jemand ein Emoji in ein Kommentarfeld tippt, und dann als Fehler 1366 mit einer Bytefolge, die mit F0 beginnt.

HEX ist der einzige Test, der die Frage beantwortet

Bevor irgendetwas umgestellt wird, gehört ein Blick auf die tatsächlichen Bytes. SELECT spalte, HEX(spalte), LENGTH(spalte), CHAR_LENGTH(spalte) FROM tabelle liefert alles Nötige in einer Zeile. LENGTH zählt Byte, CHAR_LENGTH zählt Zeichen, und wenn beide bei einem Wort mit Umlaut auseinanderliegen, stehen dort Mehrbyte-Zeichen.

Für das kleine ü gibt es drei aussagekräftige Muster. C3BC bedeutet korrektes UTF-8. Das einzelne Byte FC bedeutet latin1, was in Ordnung ist, solange die Spalte auch als latin1 deklariert ist und die Verbindung dazu passt. C383C2BC bedeutet doppelte Kodierung, also ein Umrechnungsschritt zu viel. Mit diesen drei Mustern und der Spaltendefinition aus SHOW CREATE TABLE lässt sich jeder Fall zuordnen.

Wichtig ist, dabei auf Spaltenebene zu schauen und nicht auf Tabellenebene. Eine Tabelle kann seit Jahren utf8mb4 als Voreinstellung tragen, während einzelne alte Spalten weiterhin latin1 sind, weil ALTER TABLE mit CHARACTER SET nur die Voreinstellung ändert. SELECT COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME = 'tabelle' zeigt das für alle Spalten auf einen Blick.

Die Verbindung ist die Stelle, die am häufigsten vergessen wird

Drei Variablen bestimmen, was zwischen Client und Server passiert: character_set_client für das, was hereinkommt, character_set_connection für die Umrechnung im Server und character_set_results für das, was hinausgeht. SET NAMES utf8mb4 setzt alle drei gemeinsam, SHOW VARIABLES LIKE 'character\_set\_%' zeigt den aktuellen Stand.

In Anwendungen gehört diese Einstellung in die Verbindungsangabe des Treibers und nicht in eine SQL-Anweisung nach dem Verbindungsaufbau. Der Unterschied wird sichtbar, sobald ein Verbindungspool im Spiel ist oder ein zweiter Codepfad eine eigene Verbindung öffnet, denn dann greift eine vergessene Anweisung nicht mehr und ein Teil der Schreibvorgänge läuft mit der falschen Einstellung.

Dieselbe Falle steckt in Sicherungen. mysqldump nimmt einen Zeichensatz für den Export an, und wenn dieser nicht zum Bestand passt, entsteht eine Datei, die beim Einlesen doppelt kodiert. Beim Sichern und beim Einspielen gehört --default-character-set deshalb ausdrücklich gesetzt, und zwar auf beiden Seiten gleich. Prüf im Zweifel den Kopf der Dumpdatei, dort steht die verwendete Einstellung.

Eine bestehende Tabelle umstellen, ohne den Bestand ein zweites Mal zu beschädigen

Der Ablauf beginnt immer mit einer Kopie. Leg die betroffene Tabelle mit CREATE TABLE kopie LIKE original und INSERT INTO kopie SELECT * FROM original neu an und probier den Umbau dort. Nur so siehst du vorher, was die gewählte Anweisung mit deinen Daten tatsächlich macht.

Sind die Bytes korrekt und nur die Deklaration falsch, ist der Umweg über den Binärtyp der richtige Weg: ALTER TABLE auf VARBINARY, danach ALTER TABLE auf VARCHAR mit CHARACTER SET utf8mb4. Dabei bleibt die Bytefolge unangetastet und wird nur neu ausgelegt. Sind Bytes und Deklaration konsistent und du willst wirklich umrechnen, ist ALTER TABLE mit CONVERT TO CHARACTER SET utf8mb4 die richtige Wahl.

Zwei Nebenwirkungen gehören eingeplant. Erstens die Indexlänge: Bei utf8mb4 rechnet MySQL mit vier Byte je Zeichen, ein Index über VARCHAR(255) belegt damit 1.020 Byte. Mit dem heute üblichen Zeilenformat DYNAMIC ist das kein Problem, ältere Tabellen mit COMPACT oder REDUNDANT stoßen an die Grenze von 767 Byte und melden, der Schlüssel sei zu lang. Zweitens die Laufzeit: CONVERT TO schreibt die Tabelle neu, bei großen Tabellen gehört das in ein Wartungsfenster oder auf ein Werkzeug, das die Änderung im laufenden Betrieb durchführt.

Dazu passende Kurse

Wer Tabellen selbst entwirft, legt den Zeichensatz am besten von Anfang an fest, und wie das zusammen mit Schlüsseln und Indizes aussieht, zeigen die MySQL-Kurse zu Zeichensätzen und Datenmodell .

Wissen prüfen

Wie sicher bist du beim Thema wirklich?

Lesen fühlt sich schnell nach Können an. Ein kurzer Test zeigt dir, was davon schon sitzt und wo sich ein Kurs lohnt. Kostenlos, ohne Anmeldung, mit einer Erklärung zu jeder Antwort.

Super Schulung, in der der Trainer auch auf eigene Fragestellungen eingegangen ist.
PostgreSQL Datenbankentwicklung Aufbaukurs
Super Grundlagenschulung mit allen wichtigen Punkten und hilfreichen Tipps!
PostgreSQL Einführung und SQL Grundlagenkurs
Schön, sich 5 Tage ungestört mit Postgres beschäftigen zu können.
PostgreSQL für Administratoren Grundkurs

Häufige Fragen

Woran erkenne ich, ob nur die Anzeige oder schon die Daten kaputt sind?
An der Bytefolge. Führ SELECT HEX(spalte) auf einem betroffenen Wert aus. Steht dort für ein ü die Folge C3BC, sind die Daten korrektes UTF-8 und das Problem liegt in der Verbindung oder in der Ausgabe. Stehen dort vier Byte, C383C2BC, liegt doppelte Kodierung vor und der Bestand muss umgerechnet werden.
Reicht es, die Tabelle auf utf8mb4 zu ändern?
Nur wenn du die richtige Anweisung nimmst. ALTER TABLE t CHARACTER SET utf8mb4 ändert lediglich die Voreinstellung für künftig angelegte Spalten, die bestehenden bleiben unverändert. Den Inhalt umgerechnet bekommst du mit ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4, und das ist nur dann richtig, wenn die Bytes bisher wirklich zur alten Deklaration passten.
Warum treten die Probleme nach einem Serverumzug plötzlich auf?
Weil sich dabei fast immer eine Voreinstellung ändert. MySQL 8 verwendet standardmäßig utf8mb4, ältere Versionen latin1, und viele Treiber übernehmen die Servervoreinstellung, wenn in der Verbindungsangabe nichts steht. Solange Server und Anwendung konsistent falsch eingestellt waren, ist nichts aufgefallen. Nach dem Umzug passt eine Seite nicht mehr zur anderen.
Wie verhindere ich, dass das noch einmal passiert?
Indem an allen vier Stellen dasselbe steht und du das nachprüfst. Der Server bekommt character_set_server auf utf8mb4, jede Tabelle und jede Spalte ebenfalls, die Verbindungsangabe des Treibers enthält den Zeichensatz ausdrücklich, und Sicherungen laufen mit gesetztem --default-character-set. Eine kurze Abfrage über information_schema.COLUMNS, die alle Spalten mit abweichendem Zeichensatz auflistet, gehört in die regelmäßige Prüfung.
Persönlich für dich da

Deine Ansprechpartner

Du bist dir nicht sicher, welcher Kurs oder welches Level zu dir passt? Wir beraten dich persönlich und kostenlos.

Yves Hoppe

Yves Hoppe

Weiterbildung & Beratung

Hilft dir, aus dem Datenbanken-Programm den passenden Kurs für deinen Stand zu finden.

Norbert Jansen

Norbert Jansen

Beratung & Inhouse

Plant mit dir Inhouse-Trainings, die auf eure Abläufe und euren Datenbestand zugeschnitten sind.

Zeichensätze, die man einmal richtig aufsetzt

Wie eine Datenbank von der Spaltendefinition bis zur Sicherung durchgängig auf utf8mb4 steht, richtest du bei cmt an einem eigenen Bestand ein, samt der Prüfung, ob wirklich alles umgestellt ist.