PHP und UTF-8 - eine Anleitung, Teil 1: MySQL

Am Anfang der Serie über PHP und UTF-8 soll zunächst ein Abstecher zu MySQL stehen, schließlich ziehen die meisten PHP-Anwendungen ihre Daten aus einer MySQL-Datenbank. Und anders als man vielleicht annehmen würde, ist die Verwendung von UTF-8 mit MySQL alles andere als problemfrei - insbesondere reicht es nicht aus, Tabellen einfach als UTF-8 zu deklarieren!

Das Gemeine an einer fehlerhaften Verwendung von UTF-8 im Zusammenspiel von PHP und MySQL ist, das diese nicht sofort auffällt. In der Tat gibt es nur einen wirklichen Testfall für eine richtige UTF-8-Integration, und das ist die Erkennung mit dem LIKE-Operator und dem Buchstaben „a“.

Hier ist ein Testcase. Der Testcase wurde auf einem Linux-System ausgeführt, das UTF-8 als Systemzeichensatz benutzt. Auf System mit anderem Systemzeichensatz kann das Verhalten reproduziert werden, indem die SQL-Statements in eine Textdatei geschrieben werden, die als UTF-8 gespeichert und dann im MySQL-Client ausgeführt wird.

Starten Sie den MySQL-Client aus der Kommandozeile (nein – PHPMyAdmin, MySQL Query Browser oder andere Tools zählen nicht!):

mysql -uroot -p[rootpasswort] --default-character-set=LATIN1</code>

und geben Sie folgende Zeilen ein:

create database utf8test default character set 'UTF8';
use utf8test
create table sorttest (value varchar(15)) default character set 'UTF8';
insert into sorttest (value) values ('aaa');
insert into sorttest (value) values ('üüü');
select * from sorttest where value like 'a%';</code>

Das erwartete Ergebnis ist natürlich eine Zeile, nämlich „aaa“ – wir erhalten jedoch zwei: „aaa“ und „üüü“!

+--------+
| value  |
+--------+
| aaa    |
| üüü    |
+--------+

Wie kann das angehen?

Der Grund liegt natürlich im Parameter –default-chararacter-set=LATIN1. Machen wir mal die Gegenprobe, indem wir explizit UTF-8 als Standard setzen. Dazu öffnen wir wieder die MySQL Kommandozeile:

mysql -uroot -p[rootpasswort] --default-character-set=UTF8</code>

und geben zunächst folgendes ein:

use utf8test
select * from sorttest where value like 'a%';

Wir bekommen wieder zwei Ergebnisse, diesmal aber sehen wir etwas anderes:

+--------------+
| value        |
+--------------+
| aaa          |
| üüü       |
+--------------+

Hier wird zunächst klar, warum wir zwei Ergebnisse für die Abfrage „Beginnt mit A“ bekommen: „Ó wird auch unter „a“ eingeordnet. Gleichzeitig sehen wir, was wirklich in die Tabelle geschrieben wurde: Der UTF-8-Code für „Ó und der UTF-8-Code für „¼“, statt wie erwartet die ANSI-Zeichen-Kombination „ü“, die den UTF-8-Code für „ü“ bildet.

Wir haben damit die wunderbare Welt der MySQL-Zeichensatzkonvertierung betreten. Machen wir aber zunächst weiter mit der Gegenprobe:

create table sorttest2 (value varchar(15)) default character set 'UTF8';
insert into sorttest2 (value) values ('aaa');
insert into sorttest2 (value) values ('üüü');
select * from sorttest2 where value like 'a%';</code>

Wir bekommen das korrekte Ergebnis:

+-------+
| value |
+-------+
| aaa   |
+-------+

Wir haben in obigen Beispielen die Zeichenkodierung des MySQL-Clients gesetzt. Lassen wir den Parameter –default-character-set“ weg, wird die Standard-Kodierung genutzt. Sofern diese nicht manuell angepasst worden ist (was selten passiert) ist das Latin1 mit der Sortierung „Schwedisch“!

Dröseln wir jetzt mal auf, was da genau passiert ist:

  1. Wir sind auf einem Linux-System mit UTF-8 als Zeichensatz. Die Eingabe „üüü“ wird daher in die ANSI-Zeichen „üüü“ umgesetzt.
  2. Der MySQL-Client erwartet Latin1, liest also nicht „üüü“, sondern eben „üüü“.
  3. Und dies schickt er auch an den Server, und zwar mit der Anmerkung, es handele sich hier um Daten im Format Latin1.
  4. Der Server wiederum weiß, dass die Tabelle UTF-8 benutzt und konvertiert entsprechend die ankommenden Daten von Latin1 nach UTF-8. Er speichert also nicht ein UTF-8-"ü", sondern UTF-8-„üüü“
  5. Wir fragen die Tabelle ab und verlangen dabei das Format Latin1.
  6. Der Server gibt uns die Daten zurück, konvertiert aber vorher von UTF-8 nach Latin1, weil der Client das so wollte.
  7. Der Client erhält „üüü“ als ANSI und druckt das aus.
  8. „üüü“ werden auf dem Bildschirm als „üüü“ angezeigt.

Offensichtlich gibt es hier zwei Knackpunkte:

  • Die Zeichenkodierung der Eingabe
  • Die Konvertierungen auf dem Weg vom Client zum Server

Wenn wir eine PHP-Seite betreiben, die ihre Daten als UTF-8 ausgibt, werden auch die Eingaben aller Forms als UTF-8 vorliegen. Sobald wir diese in die Datenbank speichern wollen haben wir effektiv die gleiche Situation wie im obigen Testcase, da MySQL mit hoher Wahrscheinlichkeit „Latin1/Schwedisch“ erwartet.

Wir müssen also MySQL aus PHP heraus irgendwie mitteilen, dass UTF-8-Daten kommen, und das geht über das „SET NAMES“- bzw. das „SET CHARSET“-Kommando. Direkt nach der Verbindung zur Datenbank senden wir dazu folgendes SQL-Statement:

set names 'utf8';

Dies entspricht dem Parameter –default-character-set=“UTF8“ beim MySQL-Client.

Das wars.

Naja, nicht ganz. Wir können natürlich an der MySQL-Konfiguration herumschrauben und dort UTF-8 als Standard setzen. Hier der entsprechende Auszug aus meiner MySQL-Konfiguration :

[mysql]
default-character-set=utf8

[mysqld]
collation_server=utf8_unicode_ci
character_set_server=utf8

Allerdings kann das zu unangenehmen Überraschungen führen, wenn das System migriert wird. Und wenn erst einmal richtige UTF-8 und ANSI-UTF-8-Mischmasch-Daten in einer Tabelle stehen ist Spaß garantiert.

Des Weiteren sollte man sich angewöhnen, alle MySQL-Clients mit dem Systemzeichensatz als Zeichensatz zu starten, im Fall eines UTF-8-basierten Linux also so:

mysqldump --default-character-set="UTF8" -u... -p...
mysql --default-character-set="UTF8" -u... -p...
mysqlimport --default-character-set="UTF8" -u... -p...

Published: November 22 2007