Vor Kurzem wollten wir einen Datenbank-Export (einen sogenannten Dump), den uns ein externer Zulieferer geschickt hatte, in eine unserer Datenbanken importieren. Das ist in unserem Geschäft nicht weiter ungewöhnlich. In diesem Fall zeigte sich jedoch, dass der Dump nicht vollständig kompatibel mit unseren Datenbank-Systemen war: Der Dump verwendete eine Collation, die nur MySQL kannte, und wir mussten ihn in MariaDB importieren.

Um die beste Lösung für dieses Problem zu finden, haben wir unser Know-How über Unicode, Zeichensätze allgemein, Sortierreihenfolgen und so weiter etwas aufgefrischt. Das ist eine gute Gelegenheit, hier im Blog einen kurzen Überblick über das Thema zu geben - und im Zuge dessen auch zu erklären, was eigentlich eine Collation ist.

Was soll das eigentlich heißen?

Um das zu tun, müssen wir aber zunächst weit zurück gehen, fast bis ganz zum Anfang: Wie speichert ein Computer eigentlich Informationen? Nun, die meisten werden schon Begriffe wie “binär” oder “Bit” gehört haben. Und die bilden auch die Grundlagen hier: Ein Computer kennt zunächst nur Strom aus oder Strom an, nur 0 oder 11. Das ist die Information, die in einem Bit gespeichert werden kann.

Und wie kommen wir jetzt von “0 oder 1” zu all den Inhalten, die ein Computer offensichtlich speichern und verarbeiten kann, wie beispielsweise Texte, Bilder, Musik, Videos und so weiter? Hier ist das Schlüsselwort die Konvention: Irgendwann hat man sich geeinigt, welche Abfolgen von 0 und 1 was bedeuten sollen. Diese Abfolge soll als die Farbe Rot interpretiert werden, jene Abfolge als der Buchstabe “H”, diese Abfolge als ein Ton mit einer bestimmten Tonhöhe und so fort. In unterschiedlichen Kontexten kann dieselbe Abfolge von Bits unterschiedliche Bedeutungen haben: Bei einem Text wird 01000001 mit einiger Wahrscheinlichkeit für ein “A” stehen, in einer Audiodatei hat es vermutlich eher eine andere Bedeutung.

Im Bereich von Texten, also für Buchstaben und andere Zeichen, sind wichtige Konventionen die sogenannten “Zeichensätze”. Ein Zeichensatz ist im Prinzip eine Tabelle, welches Zeichen durch welche Zahl dargestellt werden soll. Eine der wichtigsten Zeichensätze in der westlichen Welt, auf dem die meisten heute verwendeten Konventionen in diesem Bereich beruhen, ist der American Standard Code for Informationen Interchange oder kurz ASCII. Wenn ich also ein “A” tippe, wird laut ASCII die Zahl 65 abgespeichert, bei einem “B” die Zahl 66 und so weiter2.

ASCII arbeitet mit 7 Bit, kann also maximal 128 unterschiedliche Zeichen darstellen. Das sind neben den Groß- und Kleinbuchstaben des lateinischen Alphabets die arabischen Ziffern, verschiedene Satzzeichen und Sonderzeichen sowie rund 30 nicht druckbare Steuerzeichen. Damit kann man schon einiges anfangen, aber einige Probleme bleiben, nicht zuletzt auch aus deutscher Perspektive: ASCII enthält keine Umlaute wie “ä” oder “ö”, und kein “ß”. Das heißt, in reinem ASCII kann man das Wort “Lösung” gar nicht schreiben, man muss auf beispielsweise “Loesung” ausweichen. Sonderlich “schoen” ist das nicht. In anderen Sprachen gibt es ähnliche Probleme, wenn beispielsweise Franzosen einen Accent Aigu verwenden wollen, oder Tschechen einen Hatschek - von Sprachen, die das lateinische Alphabet gar nicht verwenden, ganz zu schweigen.

Entsprechend wurden schon relativ bald abgewandelte Versionen und Erweiterungen von ASCII entworfen. Zu den wichtigsten davon gehört die Normenfamilie ISO 8859. Diese Gruppe von Standards definiert insgesamt 15 Zeichensätze mit 8 Bit. Die ersten 128 Positionen in diesen Zeichensätzen entsprechen dem bereits bekannten ASCII. In der zweiten Hälfte finden sich zusätzliche Zeichen, die in bestimmten Sprachen oder Regionen benötigt werden. Beispielsweise enthält ISO 8859-1 Zeichen, die in Westeuropa üblich sind. ISO 8859-2 deckt Mitteleuropa ab, ISO 8859-3 Südeuropa und ISO 8859-4 Nordeuropa. Dazu kommen Versionen für Kyrillisch, Arabisch, Griechisch, Hebräisch, Thai und andere. Für einige dieser Zeichensätze sind auch Namen wie “Latin-1”, “Latin-2” oder “Latin-9” gebräuchlich.

Es wird richtig, richtig groß

Das ist schon mal ein großer Schritt vorwärts, jedenfalls für diejenigen, deren Bedürfnisse über ASCII hinaus von ISO 8859 abgedeckt werden. Aber es ist noch kein Zeichensatz, der wirklich alles enthält. Einer der ersten Ansätze für eine solche “universelle Zeichencodierung” aus dem Jahr 1988 sollte “nur” die Zeichen moderner Sprachen umfassen. Dafür, so Initiator Joseph D. Becker damals, sollten doch 14 Bit oder 16.384 Plätze in der Zuordnungstabelle sicherlich ausreichen. Entsprechend sah man einen Zeichensatz mit einer Breite von 16 Bit, also zwei Byte, vor. 1991 wurde die Version 1.0.0 des Unicode-Standards veröffentlicht. Diese Version umfasste 7.161 Zeichen aus 24 Schriftsystemen. Die ersten 256 Zeichen davon entsprechen ISO 8859-1. Nochmal kurz zur Verdeutlichung, es ging von 128 Zeichen in ASCII, 256 Zeichen je Zeichensatz in ISO 8859 und dann direkt auf mehr als 7.000 Zeichen in der ersten Fassung von Unicode.

Und da war noch lange nicht Schluss. Version 1.0.0 enthielt beispielsweise noch keine Zeichen aus ostasiatischen Schriftsystemen. Die sogenannte Han-Vereinheitlichung, also die Vereinheitlichung von chinesischen Hanzi, japanischen Kanji und koreanischen Hanja zu einem Zeichensatz, kam erst über ein halbes Jahr später. Version 1.0.1 des Unicode-Standards aus dem Sommer 1992 umfasste dann auch diese sogenannten CJK-Schriftzeichen. Der Umfang von Unicode wuchs damit von 24 auf 25 Schriftsysteme - und von 7.161 Zeichen auf 28.359 Zeichen, knapp das Vierfache.

Mit Unicode 5.1 aus dem Jahr 2009 wurde erstmals die Grenze von 100.000 Zeichen überschritten, diese Version umfasste 100.713 Zeichen aus 75 Schriftsystemen. Die neueste Version ist Unicode 16.0 aus dem September 2024, mit 154.998 Zeichen aus 168 Schriftsystemen. Die Beschränkung auf “nur” moderne Schriftsysteme wurde längst fallengelassen, ebenso wie die Breite von 16 Bit. Denn das würde “nur” Platz für 65.536 Zeichen bieten. Das hätte Unicode bereits mit Version 2.0 aus dem Jahr 1996 gesprengt. Der aktuelle technische Unterbau erlaubt bis zu 1.111.998 Zeichen in Unicode (eigentlich noch ein paar mehr, aber bestimmte Bereiche sind für verschiedene Zwecke reserviert).

Mach’ Dich nicht so breit!

In solchen Dimensionen wird eine simple Tabelle irgendwann unpraktisch. Deshalb ist der Unicode-Standard in siebzehn sogenannte Ebenen mit jeweils 65.536 Zeichen eingeteilt. Plane 0 ist die “Basic Multilingual Plane” (BMP). Dort sind hauptsächlich Schriftsysteme angesiedelt, die aktuell in Gebrauch sind. Für viele Verwendungen ist die BMP entsprechend völlig ausreichend, und manche Software bietet nur eingeschränkten oder gar keinen Zugriff auf die restlichen 16 Ebenen.

Außerdem würde eine “direkte” Zuordnung von Zeichen auf Nummer, wie zum Beispiel in ASCII, sehr viel Speicherplatz benötigen: Um alle aktuell in Unicode enthaltenen Zeichen auszudrücken, braucht man 32 Bit oder 4 Byte pro Zeichen. Das heißt, für ein “A” würde man nicht dezimal 65 oder binär 01000001 abspeichern, sondern 00000000 00000000 00000000 01000001. Das wäre insbesondere bei Texten, die vor allem aus ASCII- oder Latin-1-Zeichen bestehen, eine enorme Platzverschwendung.

Dieses Problem lösen die Unicode Transformation Formate oder UTF. Das einfachste UTF speichert tatsächlich die Nummer jedes Zeichens (oder, wie es in Unicode heißt, den Codepunkt) mit 32 Bit ab. Dieses UTF32 ist aber, wie wir gesehen haben, so verschwenderisch, dass es in der Praxis de facto nicht vorkommt.

Andere UTF verwenden unterschiedlich viel Speicherplatz pro Zeichen, je nachdem, um welches Zeichen es sich handelt. UTF16 arbeitet mit einer oder zwei Einheiten von je 16 Bit pro Zeichen. Auf dieses Format trifft man hin und wieder, aber auch nur selten. Der weit überwiegende Platzhirsch ist UTF8. Dieses Format verwendet zwischen einem und vier Bytes, also 8 bis 32 Bit, pro Zeichen.

In MySQL und MariaDB spielen insbesondere zwei “Speicherbreiten” eine Rolle, nämlich drei und vier Bytes. In Zeichensätzen, die utf8 im Namen tragen, können maximal 3 Byte pro Zeichen verwendet werden. Damit kann man keine Zeichen außerhalb der BMP speichern. Dafür braucht man Zeichensätze mit bis zu vier Byte pro Zeichen. Diese erkennt man an utf8mb4 im Namen. Ein typischer Anwendungsfall für Zeichensätze mit utf8mb4 sind Emojis, die hauptsächlich in plane 1 einsortiert sind.

A, B, C, Ü, 字

Bleibt noch ein Problem: Wie sortiert man das alles? Die meisten, zumindest wenn sie im westlichen Kulturkreis aufgewachsen sind, haben irgendwann einmal das ABC gelernt. Diese festgelegte Reihenfolge der Buchstaben von A bis Z erlaubt es uns, beispielsweise Wörter alphabetisch zu sortieren, also nach der Ordnung des lateinischen Alphabets. Aber was, wenn man sich aus dem lateinischen Alphabet (im engeren Sinne) hinausbewegt? Wo sortiert man beispielsweise deutsche Umlaute ein, oder Ziffern, oder Zeichen mit Akzenten wie “é” oder “ç”? Nicht zu reden von Schriftsystemen, bei denen es nicht die eine festgelegte Sortierreihenfolge gibt, etwa japanische Kanji.

Als Oberbegriff für solche Regeln wie das A, B, C spricht man von “Einsortierungsregeln” oder im Englischen Collation. Und damit sind wir wieder beim Anfang dieses Blogbeitrags: Eine Collation ist ein System von Regeln, wie Inhalte in einem bestimmten Zeichensatz sortiert werden sollen. Für Unicode gibt den Unicode Collation Algorithm oder kurz UCA.

Der Dump, den wir einspielen wollte, verwendete die Collation utf8mb4_0900_ai_ci. Der Name verrät bereits einiges über diese Sortierregeln: Die Collation verwendet UTF8 mit bis zu vier Bytes pro Zeichen (utf8mb4). Sie beruht auf dem Unicode-Standard in Version 9.0.0 (0900). Sie ignoriert beim Sortieren Akzente (“accent insensitive”, ai). Das heißt, ein “é” wird wie ein “e” sortiert. Und sie ignoriert Groß- und Kleinschreibung (“case insensitive”, ci). Ein “E” wird also wie ein “e” einsortiert.

Das Problem war nun eben, diese Collation wird in MySQL (ab Version 8.0.1) unterstützt, in MariaDB aber nicht. Es gibt auch keine direkte Entsprechung. Wir mussten also den Dump “umschreiben” und in den SQL-Statements utf8mb4_0900_ai_ci durch eine andere Collation ersetzen. Aber durch welche?

Einfach pauschal mit utf8_general_ci drüberzubügeln, wäre die einfachste Lösung gewesen. Aber davon wird häufig abgeraten, weil diese Collation an einigen Stellen (nach heutigem Verständnis) falsch sortiert, und außerdem seit Jahren überholt ist.

Recht schnell kristallisierten sich drei Kandidaten heraus: utf8mb4_unicode_ci, utf8mb4_unicode_520 und uca1400_ai_ci. Diese drei unterscheiden sich vor allem darin, welcher UCA-Version (nicht Unicode-Version) sie folgen: utf8mb4_unicode_ci basiert auf UCA 4.0.0, utf8mb4_unicode_520 auf UCA 5.2.0, und uca1400_ai_ci auf UCA 14.0.0.

Die letztgenannte Collation ist entsprechend die aktuellste, und weil die verwendete MariaDB-Version sie unterstützt, haben wir uns dafür entschieden. Um also (endlich) die Frage vom Anfang dieses Blogbeitrags zu beantworten, wir haben den Datenbank-Dump von utf8mb4_0900_ai_ci auf uca1400_ai_ci umgeschrieben und konnten ihn dann ohne weitere Probleme verwenden.

  1. Technisch ist es nicht ganz Strom aus oder Strom an, sondern eher etwas wie Strom mit etwas niedrigerer Spannung oder Strom mit etwas höherer Spannung. Und wir reden hier von “herkömmlichen” Computern, Dinge wie Quantencomputer sind nochmal ein ganz eigenes Thema. 

  2. Einen Teil der Erklärung überspringen wir hier, nämlich wie man von “0 oder 1” zu Dezimalzahlen wie 65 kommt. Eine Einführung in die Stellenwertsysteme würde dann doch den Rahmen sprengen.