Antworten auf Ihre häufigsten Fragen

MySQL und Zeichensätze

English version below

Bei der Verwendung von MySQL 5 kommt es immer wieder zu Problemen mit der Darstellung von Web-Seiten in verschiedenen Zeichensätzen. Dies liegt daran, dass MySQL 5 Unicode-fähig ist. Dadurch ist es erheblich einfacher geworden, mehrsprachige Web-Seiten anzubieten, es ist jedoch auch erforderlich, auf korrekte Zeichenkodierungen zu achten.

Der richtige Zeichensatz in HTML-Dokumenten

Ein weit verbreiteter und oft ausreichender Zeichensatz für z.B. westeuropäischen Inhalt ist ISO-8859-1 (auch bekannt unter „Latin-1“). Er stammt aus der ISO-8859-Familie und beinhaltet unter Anderem die deutschen Umlaute und einige relevante Sonderzeichen. Wird das Dokument mit diesem Zeichensatz ausgeliefert, können alle darin enthaltenen Zeichen, mit Ausnahme einiger Zeichen, die in HTML eine besondere Bedeutung besitzen (z.B. <, > und &), direkt im HTML-Quelltext verwendet werden.

Da ISO-8859-1 nur ein Byte zur Kodierung eines Zeichens verwendet, können damit nur 256 Zeichen dargestellt werden. Was tut man aber, möchte man auf seiner Web-Seite Zeichen darstellen, die in dem gewählten Zeichensatz nicht enthalten sind? Eine Möglichkeit ist es, diese Zeichen mit Hilfe einer speziellen numerischen Notation einzufügen. Mit dieser Notation lässt sich jedes Zeichen aus dem Unicode-Zeichenvorrat notieren. Das große, griechische Sigma (Σ) z.B. ist in ISO-8859-1 nicht enthalten, es kann also nicht im Quelltext verwendet werden, der Browser würde ein anderes Zeichen darstellen. Um es dennoch darzustellen, könnte die angesprochene numerische Notation verwendet werden, in diesem Fall „Σ“. Diese Praktik ist jedoch, wenn überhaupt, nur für einzelne Zeichen sinnvoll.

UTF-8 für mehrsprachige Web-Seiten

Sollen ganze Sätze einer anderen Sprache dargestellt werden, bietet sich ein Zeichensatz mit einem größeren Zeichenvorrat an. UTF-8 z.B. kodiert Zeichen nicht in einem, sondern in einer Variablen Länge von Bytes - dadurch lassen sich alle Zeichen des Unicode-Systems darstellen. Sie können also direkt in den HTML-Quelltext notiert werden, sofern der verwendete Editor selbst UTF-8 unterstützt.

Mit welchem Zeichensatz die empfangene Ressource zu dekodieren ist, erfährt der anfordende Browser vom Webserver. Dazu muss der Webserver erfahren, dass er diese Angabe mitsenden soll, was auf verschiedene Arten geschehen kann:

  • in PHP steht dazu die Funktion header() zur Verfügung. Mittels header('Content-Type: text/html; charset=utf-8'); wird dem Webserver der verwendete MIME-Typ und Zeichensatz mitgeteilt.
  • in einer .htaccess-Datei lässt sich dem Webserver mittels AddCharset mitteilen, bestimmte Ressourcen mit einem definierten Zeichensatz auszuliefern. So veranlasst die Zeile AddCharset utf-8 .html .php den Webserver, sämtliche Dokumente mit der Datei-Endung html und php als Ressourcen mit dem Zeichensatz UTF-8 auszuliefern. Möchte man sämtlichen Antworten, die mit dem MIME-Typ text/plain und text/html ausgeliefert werden, außerdem den Zeichensatz UTF-8 vorgeben, genügt eine Zeile, die die Anweisung AddDefaultCharset enthält: AddDefaultCharset utf-8

Damit auch nach dem Abspeichern des Dokuments noch der korrekte Zeichensatz verwendet wird, gibt es außerdem die im HTML-Quelltext zu notierende Meta-Angabe zum Zeichensatz http-equiv="content-type". Diese sollte zwar auch vom Webserver interpretiert werden, allerdings nur, wenn kein HTTP-Header angegeben ist - es würde keinen Sinn ergeben, eine hiervon abweichende Angabe zu notieren. Im Konfliktfall sollte vom Browser die Angabe im HTTP-Header den Vorzug erhalten, was dann zu der Merkwürdigkeit führen könnte, dass das gleiche Dokument, über den Webserver ausgeliefert, im Unterschied zu einem lokalen Aufruf anders dekodiert würde, denn lokal aufgerufen steht dem Browser natürlich kein HTTP-Header zur Verfügung und er würde sich wohl an die Angabe im Meta-Tag halten. Die Information, ob und welche Zeichensatz-Angabe im HTTP-Header enthalten ist, kann auf mehrere Arten festgestellt werden: viele Browser zeigen die vom Server mitgelieferte Kodierungsangabe in ihren Seiteninformationen. Außerdem gibt es sogenannte HTTP-Trace-Services wie den Web-Sniffer, welche die Header-Informationen anzeigen.

Zeichensätze und Datenbankverbindungen

Die Daten werden von MySQL intern standardmäßig in UTF-8 kodiert abgelegt, die Daten zwischen Server und Client aber in Latin-1 ausgetauscht. Soll die Webseite in UTF-8 sein, muss man dem Datenbankserver mitteilen, dass die Daten als UTF-8-kodiert ankommen und auch ausgeliefert werden sollen. Das geschieht mit der SQL-Anweisung SET NAMES 'utf8' oder SET CHARACTER SET utf8.

Die gleichen Angaben müssen auch beim Im-/Export von Daten, z.B. über den phpMyAdmin, angegeben werden. Bei DB-Dumps, die von einem MySQL-Server erstellt wurden, wird der Zeichensatz der Daten (bei deutschsprachigen Daten i.d.R. Latin-1) unter „Zeichencodierung der Datei“ ausgewählt.

„MÃŒller“ statt „Müller“

Trotzdem kommt es vor, dass auf MySQL aufbauende Web-Anwendungen die Umlaute verhunzen. Das ist in der Regel eine Folge von nicht korrekt kodiert übertragenen bzw. gespeicherten Daten. Haben wir beispielsweise ein HTML-Formular, muss dem Browser mitgeteilt werden, welchen Zeichensatz er für dieses Formular akzeptieren darf. Soll die Web-Seite als ISO-8859-1 kodiert ausgeliefert werden, der Browser sendet aber fälschlicherweise UTF-8, kommt es zu oben genannter Abweichung: der Browser sendet die Byte-Sequenz, die in ISO-8859-1 „MÃŒller“ entspricht. Gelangt diese Bytesequenz ohne Überprüfung in die Datenbank, setzt sich der Fehler fort. Um das zu verhindern, gibt es mehrere Möglichkeiten:

  • der Browser darf nur ISO-8859-1 akzeptieren: im einleitenden Formular-Element benutzt man dazu das Attribut accept-charset mit dem Wert ISO-8859-1, also z.B. <form action="speichern.php" method="post" accept-charset="ISO-8859-1">. Dieses Vorgehen hat den Nachteil, dass einige Browser Fragezeichen für eingegebene Zeichen außerhalb von ISO-8859-1 senden. Der Speichervorgang in einem verarbeitenden PHP-Script könnte beispielsweise mit folgenden Zeilen geschehen:

    <?php
    $link = mysql_connect('host', 'user', 'pass')
        or die ('Verbindung zur DB nicht m&ouml;glich');
    mysql_query('SET NAMES \'latin1\'', $link);
    mysql_query('INSERT INTO datenbank.tabelle (spalte) ' .
        'VALUES (\'' . mysql_real_escape_string($daten, $link) . '\')', $link)
        or die('INSERT fehlgeschlagen: ' . mysql_error($link));
    ?>

     
  • man akzeptiert und speichert generell UTF-8, indem im Formular-Element accept-charset="UTF-8" und unter MySQL die Anweisung SET NAMES 'utf8' genutzt werden. Aber auch hier können bei der Auslieferung mittels Latin-1 Zeichen verloren gehen, da Latin-1 einen kleineren Zeichenvorrat als UTF-8 besitzt.

Die Sortierung (collation) der Daten

Die Standardeinstellung für die Sortiermethode auf unseren Servern entspricht „latin1_german2_ci“. Dies liefert die für die meisten Kunden erwartete Sortiermethode. Dies gilt bei der Verwendung von UTF-8 nicht, hier gibt es eigene Sortiervorschriften. Bei der Verwendung der standardmäßigen „utf8_general_ci“ wird aber augenscheinlich „falsch“ sortiert, Umlaute und das ß wandern nach hinten, da hier nach den Byte-Werten sortiert wird und die genannten Zeichen hinter dem z eingeordnet sind. Leider bietet MySQL von Haus aus noch keine deutsche Sortiervorschrift für UTF-8-Daten. Man kann sich aber mit einem Trick behelfen, indem die Daten für den Sortiervorgang in den gewünschten Zeichensatz mit der gewünschten Sortiervorschrift konvertiert werden:

SELECT utf8_spalte FROM tabelle
ORDER BY CONVERT(utf8_spalte USING latin1) COLLATE latin1_german2_ci ASC;

Für die Sortierung werden hier die UTF-8-Werte in den Zeichensatz Latin-1 mit der Sortiervorschrift latin1_german2_ci konvertiert, die dafür sorgt, dass ä, ö, ü und ß bei der Sortierung wie ae, oe, ue und ss behandelt werden.

Beispiele

Eine Seite mit ausschließlich chinesischem Inhalt (z.B. Zeichensatz GB2312) könnte folgendermaßen aufgebaut sein:

<?php header('Content-Type: text/html; charset=GB2312'); ?>
<html>
  <head>
    <title>GB2312-Example</title>
    <meta http-equiv="Content-Type" content="text/html; charset=GB2312" />
    <meta http-equiv="Content-Language" content="zh" />
    <meta name="DC.language" scheme="DCTERMS.RFC3066" content="zh" />
    <!-- weitere Angaben im Kopf... -->
  </head>
  <body>
    <!-- beliebiger Inhalt, die chinesischen Schriftzeichen können direkt im Quelltext notiert werden -->
<?php
 /**
  * jetzt noch irgendein dynamischer Inhalt
  */
 $link = mysql_connect('host', 'user', 'pass') or die('No Connection');
 // wir wollen mit dem MySQL-Server nur noch gb2312 sprechen
 mysql_query('SET NAMES \'gb2312\'', $link);
 $result = mysql_query('SELECT gb2312_spalte FROM datenbank.tabelle...', $link); // Ergebnisse ausgeben
?>

Weitere ausführliche Erläuterungen zu Zeichensätzen und Kollationen bei MySQL finden Sie in diesem Artikel.

 

 

English version:

When using MySQL 5, there are always problems with the display of web pages in different character sets. This is because MySQL 5 is Unicode aware. This has made it considerably easier to offer multilingual web pages, but it is also necessary to ensure that the character encodings are correct.

The correct character set in HTML documents

A widespread and often sufficient character set for e.g. Western European content is ISO-8859-1 (also known as "Latin-1"). It comes from the ISO-8859-family and includes, among other things, the German umlauts and some relevant special characters. If the document is delivered with this character set, all the characters it contains can be used directly in the HTML source text, with the exception of a few characters that have a special meaning in HTML (e.g. <, > and &).

Since ISO-8859-1 only uses one byte to encode a character, it can only represent 256 characters. But what do you do if you want to display characters on your web page that are not contained in the selected character set? One possibility is to insert these characters using a special numeric notation. Any character from the Unicode character set can be noted with this notation. For example, the uppercase Greek sigma (Σ) is not included in ISO-8859-1, so it cannot be used in the source code, the browser would display a different character. To represent it anyway, the mentioned numeric notation could be used, in this case „Σ“. However, this practice only makes sense, if at all, for individual characters.

UTF-8 for multilingual web pages

If whole sentences in another language are to be displayed, a character set with a larger character set is recommended. UTF-8 for example, does not encode characters in one but in a variable length of bytes - this allows all characters of the Unicode system to be represented. They can therefore be noted directly in the HTML source text, provided the editor used supports UTF-8 itself.

The requesting browser learns from the Webserver which character set is to be used to decode the received resource. To do this, the web server must know that it should also send this information, which can happen in various ways:

  • The header() function is available in PHP for this purpose. Using header('Content-Type: text/html; charset=utf-8'); the Webserver is informed of the MIME-type and character set used.
  • In a .htaccess file, the Webserver can be informed using AddCharset to deliver certain resources with a defined character set. The line AddCharset utf-8 .html .php causes the Webserver to deliver all documents with the file extension html and php as resources with the character set UTF-8. If you also want to specify the UTF-8 character set for all responses that are delivered with the MIME type text/plain and text/html, a line containing the AddDefaultCharset statement is sufficient: AddDefaultCharset utf-8

To ensure that the correct character set is used even after the document has been saved, there is also the meta specification for the character set http-equiv="content-type" to be noted in the HTML source code. This should also be interpreted by the Webserver, but only if no HTTP header is specified - it would make no sense to make a note of a different specification. In the event of a conflict, the browser should give preference to the information in the HTTP header, which could then lead to the oddity that the same document delivered via the Webserver would be decoded differently compared to a local call, because the browser naturally prefers to call locally no HTTP header available and it would probably stick to what is specified in the meta tag. The information as to whether and which character set information is contained in the HTTP header can be determined in several ways: many browsers show the coding information provided by the server in their page information. There are also so-called HTTP trace services such as the Web-Sniffer, which display the header information.

Character sets and database connections

By default, MySQL stores the data internally in UTF-8 code, but the data is exchanged between server and client in Latin-1. If the website is to be in UTF-8, the database server must be informed that the data should arrive and be delivered in UTF-8 code. This is done with the SQL statement SET NAMES 'utf8' or SET CHARACTER SET utf8.

The same information must also be given when importing/exporting data, e.g. via phpMyAdmin. For DB dumps created by a MySQL server, the character set of the data (usually Latin-1 for German-language data) is selected under "Character encoding of the file".

„MÃŒller“ instead of „Müller“

Nevertheless, it happens that web applications based on MySQL mess up the umlauts. This is usually a consequence of incorrectly encoded transmitted or stored data. For example, if we have an HTML form, the browser must be told which character set it may accept for this form. If the web page is to be delivered as ISO-8859-1 encoded, but the browser incorrectly sends UTF-8, the deviation mentioned above occurs: the browser sends the byte sequence that corresponds to "MÃŒller" in ISO-8859-1 . If this sequence of bytes gets into the database without checking, the error continues. There are several ways to prevent this:

  • the browser may only accept ISO-8859-1: in the introductory form element, use the accept-charset attribute with the value ISO-8859-1, e.g. <form action="speichern.php" method="post" accept-charset="ISO-8859-1">. The disadvantage of this approach is that some browsers send question marks for characters entered outside of ISO-8859-1. The saving process in a processing PHP script could be done with the following lines, for example:

    <?php
    $link = mysql_connect('host', 'user', 'pass')
        or die ('Verbindung zur DB nicht m&ouml;glich');
    mysql_query('SET NAMES \'latin1\'', $link);
    mysql_query('INSERT INTO datenbank.tabelle (spalte) ' .
        'VALUES (\'' . mysql_real_escape_string($daten, $link) . '\')', $link)
        or die('INSERT fehlgeschlagen: ' . mysql_error($link));
    ?>

     
  • UTF-8 is generally accepted and saved by using the accept-charset="UTF-8" form element and the SET NAMES 'utf8' statement under MySQL. But here, too, characters can be lost during delivery using Latin-1, since Latin-1 has a smaller character set than UTF-8.

The sorting (collation) of the data

The default sorting method on our servers is "latin1_german2_ci". This provides the sorting method expected by most customers. This does not apply when using UTF-8, there are separate sorting rules here. When using the standard "utf8_general_ci", however, sorting is apparently "wrong", umlauts and the ß move backwards, since sorting is carried out here according to the byte values and the characters mentioned are classified after the z. Unfortunately, MySQL does not yet offer a German sorting rule for UTF-8 data. However, you can use a trick to help yourself by converting the data for the sorting process into the desired character set with the desired sorting rule:

SELECT utf8_spalte FROM tabelle
ORDER BY CONVERT(utf8_spalte USING latin1) COLLATE latin1_german2_ci ASC;

For sorting, the UTF-8 values are converted to the Latin-1 character set with the sorting rule latin1_german2_ci, which ensures that ä, ö, ü and ß are treated like ae, oe, ue and ss during sorting.

Examples

A page with only Chinese content (e.g. character set GB2312) could be structured as follows:

<?php header('Content-Type: text/html; charset=GB2312'); ?>
<html>
  <head>
    <title>GB2312-Example</title>
    <meta http-equiv="Content-Type" content="text/html; charset=GB2312" />
    <meta http-equiv="Content-Language" content="zh" />
    <meta name="DC.language" scheme="DCTERMS.RFC3066" content="zh" />
    <!-- weitere Angaben im Kopf... -->
  </head>
  <body>
    <!-- beliebiger Inhalt, die chinesischen Schriftzeichen können direkt im Quelltext notiert werden -->
<?php
 /**
  * jetzt noch irgendein dynamischer Inhalt
  */
 $link = mysql_connect('host', 'user', 'pass') or die('No Connection');
 // wir wollen mit dem MySQL-Server nur noch gb2312 sprechen
 mysql_query('SET NAMES \'gb2312\'', $link);
 $result = mysql_query('SELECT gb2312_spalte FROM datenbank.tabelle...', $link); // Ergebnisse ausgeben
?>

For more in-depth explanations of character sets and collations in MySQL, see this article.


otto.friedrich@hosteurope.de xanthippe.ypsilante@hosteurope.de hercules.ikarus@hosteurope.de