Nudge am 17.11.2010

Varchar Länge in MySQL

in MySQL, Tipp | Tags: MySQL, Performance, Unicode, varchar

Früher war es sehr oft üblich, varchar(255) an jede Hauswand zu sprühen, wo Text in MySQL zu speichern war. In letzter Zeit sehe ich häufig varchar(32), varchar(64) und so weiter in verschiedenen Datenbank-Layouts. Ist das besser? Welche Länge ist eigentlich gut? In diversen Foren wird über varchar diskutiert, denn es ist oft ein entscheidendes Performance-Kriterium.

varchar im Allgemeinen

Der Datentyp varchar ist ja dafür gedacht, eine variable Anzahl von Zeichen in der entsprechenden Spalte vorzuhalten. Das klingt erstmal sehr einleuchtend und flexibel. Der große Vorteil von varchar gegenüber seinem Komplement char ist also, dass bei einer Speicherung einer sehr kurzen Zeichenkette, wie “Hallo Welt”, nicht alle X vordefinierten “Plätze” gebucht werden, sondern nur soviel Speicher, wie benötigt wird. Das scheint also eine gute Sache zu sein, denn es erlaubt uns, mehr Daten zu speichern und dabei weniger Platz zu verbrauchen.

varchar in MySQL beleuchtet

Um herauszufinden, welche Länge für unsere MySQL-Applikation die Richtige ist, müssen wir natürlich wissen, wie MySQL im speziellen varchar-Spalten behandelt. Das kann sicherlich anders sein, als es Oracle’s größeres Hausprodukt oder der MSSQL-Server machen. Zunächst die historische Seite: 255 war lange Zeit die größte Anzahl, die man angeben konnte. Warum? Das kommt wohl von der verwendeten Programmiersprache, C. Dort wird eine Zeichenkette als Arrays von Bytes angelegt. Und das letzte Byte muss noch für das Abschluss-Zeichen “\0” reichen – das Null-Byte. 255 Zeichen, plus Null-Byte, das macht genau 256 Zeichen. Wer sich ein bisschen mit Computern auskennt, erkennt hier gleich eine Zweierpotenz – also eine Zahl, die sich gut in die Speicherarchitektur einpasst. Sie ist in Informatiker-Worten “aligned“, also an günstigen Speichergrenzen “ausgerichtet“. Denn der Computer verarbeitet Daten ja immer in 8, 16, 32 oder 64 Bit – da machen sich Vielfache davon gut.

Noch deutlicher wird das MySQL-Verhalten, wenn man sich die alten Borland-PascalStrings anschaut. Dort hielt das erste Byte die Länge der Zeichenkette, und anschließend kam Zeichen für Zeichen ohne End-Terminierung. Und so legt auch MySQL die varchar-Daten an. Und historisch konnte MySQL ja auch nur US-ASCII-Bytes verstehen, keine komplexen chinesischen oder japanischen Zeichen, wie sie heute dank Unicode kein Problem mehr darstellen. Jedes Zeichen war also genau 1 Byte, also 8 Bit lang. Und dieses erste Byte hielt also die Länge der dann folgenden Zeichenkette. Es war also nicht möglich, keine größere Länge als 255 in einem Byte anzugeben. Daher also auch die 255.

Mehr als 255 Zeichen

Was ist denn mit mehr Text? Zum einen gibt es die Text-Spalte (bis 65,566 Zeichen), zum anderen lässt sich der MySQL-varchar-Typ aber auch mit mehr Zeichen betreiben. Das heißt, dass aber mehr Platz vorneweg nötig ist, um die folgende Textmenge zu beschreiben. Also mindestens 2 Byte. Gibt man also varchar(2010) an, so verbraucht das genau 2012 Bytes an Platz. Das ist für die Verarbeitung vielleicht ungut. Sollte man da auf eine ausgerichtete Größe wie 2046  (2048 – 2) wechseln? Man kann hier nur munkeln und vermuten, aber im Grunde wird es nicht schlecht sein, denke ich mir.

Varchar in der Praxis

Wenn MySQL bei Abfragen mehrere Tabellen über JOINs verknüpft, so kann es an dieser Stelle noch nicht wissen, wieviel Daten pro Zeile hineinflattern werden. Also reserviert es sicherheitshalber mal die maximal mögliche Zeichenanzahl an dieser Stelle. Mit varchar(255) werden also pro JOIN-Ergebnis-Zeile in dieser Spalte 256 Bytes verbraten. Auch wenn 99% der Einträge nur “Hallo” oder “Welt” enthalten – es ist ein Jammer. Aber besser, man weiß es wenigstens vor dem Layout der DB als hinterher. Schlimmer präsentiert sich allerdings noch der MySQL-Datentyp “TEXT” oder “BLOB” – der darf nämlich in JOINs auf keinen Fall im Speicher landen, sagt MySQL. Also wandert jede Temp-Table mit TEXT oder BLOB auf die Platte – das macht richtig Laune. Nun, es gibt hier aber auch einen Trick: Man legt /tmp auf Linux-Systemen einfach als tmpfs im RAM an. Das performt auch sehr gut mit PHP-Session-Files.

Was ist denn mit den Kanji oder chinesischen Zeichen?

Tja, jetzt wird es ja noch bunter. In ein Byte passt doch nur ein ASCII-Zeichen! Also muss jeder Zeichen-Platz nun erweitert werden, damit auch diese Schriftzeichen gespeichert werden können. MySQL macht das je nach dem angegebenen Zeichensatz, Collation genannt, unterschiedlich. Wer hier UTF-8 wählt, bringt MySQL dazu, pro Zeichen ganze 3 Byte (!) einzukalkulieren. Das bedeutet, ein varchar(255) verbrät in  vollem Zustand ganze 255 * 3 = 765 + 2 = 768 Bytes. Das ist dann schon eine ganze Menge. Knapp eine halbe Schreibmaschinenseite. Damit ein UTF-8-Text noch eine ein-bytige Längenbeschreibung erhält, darf er also ganze 85 Zeichen enthalten -> varchar(85) ist also dein Freund. Wer mit UCS-2 (16 Bit) gut fährt, vermeidet variable Zeichen-Byte-Längen und kann doch über 65,000 Schriftzeichen unterstützen. Das Unicode-Level von UCS-2 ist dasselbe wie mit den 3 Byte des UTF-8 in MySQL – für volle Unicode-Unterstützung wären hier nämlich bis zu 10 Byte pro Zeichen nötig. Allerdings wächst UTF-8 in den letzten Jahren, auch dank XML, Google und Co., zu einem De-facto-Standard heran.

Der Zeichensatz kann in MySQL zunächst auf Datenbank-Ebene angegeben werden. Wird dann bei Tabellen-Anlage nichts weiter mitgeteilt, wird der Datenbank-Zeichensatz übernommen. Wird der Tabelle etwas mitgegeben, verwendet diese spezielle Tabelle einen anderen Zeichensatz. Einer Spalte kann man ebenso einen besonderen Zeichensatz mitgeben. Ohne Angabe zieht wiederum der Tabellen-Zeichensatz. Das klingt auch erstmal sehr vernünftig, denn wo man nichts braucht, so man nichts verbrauchen. Allerdings muss man aufpassen, dass man Tabellen-Schlüssel vergleichbar hält. Möchte man also zwei Tabellen in einem JOIN verknüpfen und über (Un-)Gleichheit zweier Strings zusammenführen oder sortieren, dann sollten diese Spalten dieselbe Collation besitzen. MySQL beschwert sich sonst und verweigert die Operation. Toll, oder?

Was ist mit geraden Angaben wie varchar(32)?

Wer sich im Online-Bereich bewegt, weiß, wie oft Algorithmen 32Bit zurückgeben. Ist ja auch schön “aligned”. Allerdings, wie wir jetzt wissen, würde es dazu führen, dass MySQL immer 33 Bytes verbraucht – das ist richtig doof! Total “unaligned”. Was macht man denn damit? Erweitert man das auf varchar(63) – das wäre zwar richtig gut aligned, aber auch irgendwie viel zu viel Platzverschwendung in JOINs. Vielleicht geht man hier auf varchar(35) für 36 Byte (=32 + 4) oder varchar(47) für 48 Byte (= 32 + 16) – man weiß es nicht. Da würde ich gern mehr Erfahrung sammeln, welche Methode am Ende die Nase vorn hat. Der Vorsprung könnte sich allerdings im esotherischen Bereich bewegen, da hier Platten-IO, Netzwerk, Speicher, CPU und Luftfeuchte eine komplexe Wechselwirkung eingehen. Fakt ist, das selbst in UTF-8-Datenbanken sich hier eine ASCII- oder Latin1-Kodierung gut machen, um MD5-Werte oder PHP-Session-IDs zu speichern. Alles andere ist wirklich Perlen vor die Säue.

Fazit

Man vermeide varchar, wo es geht. Denn sie klingen erst mal praktisch, aber der Ärger ist hoch. Am besten, man führt numerische Strassenbezeichnungen ein, und Familien- oder Vornamen werden auf dem Bürgeramt auf BIGINT indiziert und Kommentare auf Websites werden nur noch per Doodle-Vorwahl akzeptiert: “How do you rate this site: Good, Better, Best”. 🙂

Im Ernst, man sollte sie wirklich vermeiden. Wo man sie braucht, sollte man die Länge auf sinnvolle Weise begrenzen, auf eine minimale Kodierung achten und die Vergleichbarkeit zwischen verknüpften Tabellen sicherstellen. Um wichtige Tabellen nicht mit varchar’s zu überfrachten, kann man diese auch gut und gerne in 2 Tabellen aufsplitten: Eine sehr schmale und schnelle für die Indizes mit minimalen Angaben und möglichst fixer Zeilenbreite und eine, die wenig frequentierte, erweiterte Textangaben beinhaltet. Zu guter Letzt beachte man noch die Ausrichtung, wo es möglich ist. In dieser Reihenfolge gehandelt, kann eigentlich nichts mehr schiefgehen. Viel Erfolg mit varchar in MySQL!


Das mark ich mir: Alltagz Mr Wong Yigg Del.icio.us Yahoo MyWeb Blinklist Google folkd
 

2 responses to “Varchar Länge in MySQL”

  1. Nudge says:

    Gute Frage. 🙂

    Fall 1 und 2 sind meines Erachten klar.

    Bei Fall 3 ist die Frage, ob auch nur 1 Byte für die String-Länge bei UTF-8 verwendet wird, wenn die Gesamtbytelänge über 255 Zeichen liegt, die Anzahl der Characters aber drunter – müsste man glatt mal in den Quellcode schauen!

    Wenn es teuer wird, aligned zu fahren (weil der Unterschied zwischen tatsächlichem Platzverbrauch und nächster Alignment->Größe zu groß wird), würde ich persönlich die schmalere Spalte vorziehen, da bei JOINs immer die volle Zeichenlänge in der temp-table reserviert werden muss.

  2. Michel Zimmer says:

    1. Habe ich folgendes richtig verstanden? (und wenn nicht, wie ist X dann zu verstehen?)
    VARCHAR(X) mit X:Anzahl der Zeichen, die erhalten bleiben (bei Überlängen) / alles danach wird abgeschnitten

    2. Also wären folgende Varchars für utf8_unicode_ci in bezug auf alignment sinnvoll?

    VARCHAR(42)

    (128-1)/3=42,333333…
    42*3+1=127 [unaligned:127-128=-1]

    VARCHAR(85)

    (256-1)/3=85
    85*3+1=256 [aligned:256-256=0]

    VARCHAR(170)

    (512-2)/3=170
    170*3+2=512 [aligned:512-512=0]

Leave a Reply

Your email address will not be published. Required fields are marked *