WordPress und UTF-8 Multibyte – was passiert da eigentlich genau?

Ein Blick auf die Datenbank-Tabellen via phpMyAdmin ist häufig verwirrend für den Endnutzer. MyISAM, InnoDB und dazu noch Abkürzungen wie _ci oder mb4 bei Kollation und Zeichensatz. Was genau stelle ich da eigentlich ein und was bedeutet das, und was macht WordPress dabei im Hintergrund? In diesem Artikel schaue ich mir das mal genauer an.

Der Auslöser für diese Geschichte ist eine E-Mail-Korrespondenz mit einem Kollegen, der mir mitteilte, dass meine WordPress-Installation bei seiner Migration fehlerhafte Umlaute/Sonderzeichen produzierte und er lieferte folgende Erklärung dafür:

MySQL (und MariaDB) verwenden unter dem Namen UTF-8 leider nur einen UTF-8 Subset. Die komplette UTF-8 Unterstützung gibt es nur bei der Verwendung von „utf8mb4“. Ein schöner Artikel dazu ist hier:

https://www.hydroxi.de/utf8-vs-utf8mb4/

Deshalb arbeiten die meisten WordPress Installationen, auf die ich Zugriff habe mit:

define( 'DB_CHARSET', 'utf8mb4' );

Ich recherchierte daher, ob an dieser These etwas dran ist und schrieb dann folgendes zurück:

Die Erklärung in dem Artikel ist nur leider falsch. Ja, es ist ein Subset, aber das ist kein reparierter Fehler, sondern einfach eine Weiterentwicklung von UTF-8. Bei UTF8MB4 steht das MB4 für Multibyte und eben 4. Für das Speichern der Zeichen werden also statt 3 nun 4 Zeichen benutzt, damit wurden zum einen Japanische/Chinesische Zeichen möglich und Emojis, beides benötigt 4 Bytes.

Hier eine bessere Erklärung vom MySQL-Team direkt:
http://mysqlserverteam.com/mysql-8-0-when-to-use-utf8mb3-over-utf8mb4/

utf8mb4 wird übrigens dann von WordPress automatisch benutzt, wenn der Server beziehungsweise die MySQL/MariaDB-Version, um genau zu sein, dies unterstützt. Egal was in der wp-config.php steht.

if ( 'utf8' === $charset && $this->has_cap( 'utf8mb4' ) ) {
			$charset = 'utf8mb4';
		}

		if ( 'utf8mb4' === $charset && ! $this->has_cap( 'utf8mb4' ) ) {
			$charset = 'utf8';
			$collate = str_replace( 'utf8mb4_', 'utf8_', $collate );
		}

Siehe: WordPress auf Github

Die Funktion has_cap checkt dabei die Versionsnummer von MySQL. Da der utf8mb4-Support mit Version 5.5.3 (bzw. 5.0.9) kam, wird so die Unterstützung zurückgemeldet:

case 'utf8mb4':      // @since 4.1.0
				if ( version_compare( $version, '5.5.3', '<' ) ) {
					return false;
				}
				if ( $this->use_mysqli ) {
					$client_version = mysqli_get_client_info();
				} else {
					$client_version = mysql_get_client_info();
				}

				/*
				 * libmysql has supported utf8mb4 since 5.5.3, same as the MySQL server.
				 * mysqlnd has supported utf8mb4 since 5.0.9.
				 */
				if ( false !== strpos( $client_version, 'mysqlnd' ) ) {
					$client_version = preg_replace( '/^\D+([\d.]+).*/', '$1', $client_version );
					return version_compare( $client_version, '5.0.9', '>=' );
				} else {
					return version_compare( $client_version, '5.5.3', '>=' );
				}

Es ist somit egal, ob in der wp-config.php utf8 (Kurzform für utf8mb3) oder utf8mb4 steht, denn der Wert wird je nach Version der Datenbank entsprechend korrigiert.

***

Die faszinierende Geschichte hinter dem MB4-Support in WordPress wird übrigens in diesem Artikel (bzw. dem Video darin) erklärt. Da es hier nicht primär darum geht, erspare ich mir eine eigene Erklärung dazu. Ich kann aber jedem, der sie noch nicht kennt, empfehlen Artikel und Video anzuschauen. Es lohnt sich definitiv!
https://poststatus.com/the-trojan-emoji/

Ich habe viele Jahre WordPress nur genutzt, später angefangen es zu erweitern. Erst durch Themes, später auch durch Plugins. Viel zu spät habe ich angefangen, den Core-Code selbst zu lesen und so meine Informationen zu gewinnen. Und sehr häufig finden sich dort Fehler, aber auch spannende Hinweise.

So geht der obige Code noch weiter:

if ( 'utf8mb4' === $charset ) {
			// _general_ is outdated, so we can upgrade it to _unicode_, instead.
			if ( ! $collate || 'utf8_general_ci' === $collate ) {
				$collate = 'utf8mb4_unicode_ci';
			} else {
				$collate = str_replace( 'utf8_', 'utf8mb4_', $collate );
			}
		}

Und wir erfahren, dass utf8mb4_general_ci veraltet ist und besser utf8mb4_unicode_ci benutzt werden sollte. Das _ci am Ende steht übrigens für case insensitive, die Groß-/Kleinschreibung wird also ignoriert.

Der Code geht noch weiter und zeigt eine weitere Empfehlung:

// _unicode_520_ is a better collation, we should use that when it's available.
		if ( $this->has_cap( 'utf8mb4_520' ) && 'utf8mb4_unicode_ci' === $collate ) {
			$collate = 'utf8mb4_unicode_520_ci';
		}

Wenn utf8mb4_unicode_520_ci vorhanden ist, dann ist diese Kollation zu präferieren. Der Test geht auch hier über die MySQL-Versionsnummer. Ist diese höher oder gleich 5.6 wird diese Kollation unterstützt:

case 'utf8mb4_520': // @since 4.6.0
				return version_compare( $version, '5.6', '>=' );

Bei Kollation geht es um Sortierung. Der Unterschied von _general zu _unicode ist, dass letzteres komplexere Ersetzungen miteinbezieht (Kombinierte Zeichen, bestimmte Sonderzeichen). Zum Beispiel, dass „ß“ bei Sortierungen „ss“ gleichgesetzt ist.

Durch die komplexere Bearbeitung ist _unicode jedoch langsamer als _general. Wenn man auf diese Präzision verzichten kann, ist die Performance ggf. vorzuziehen.

Der Unterschied von _unicode zu _unicode_520 ist die Versionsnummer der unterstützen Unicode-Variante. _unicode entspricht 4.0.0. Alle weiteren Varianten enthalten die Versionsnummer im Namen _unicode_520 entspricht dann Versionsnummer 5.2.0.

Nachlesen kann man all dies in der MySQL-Doku:
https://dev.mysql.com/doc/refman/5.7/en/charset-unicode-sets.html
https://dev.mysql.com/doc/refman/5.7/en/charset-collation-names.html

Ich hoffe, dass die Einstellungen nun etwas verständlicher geworden sind und das Lesen des WordPress-Core vielleicht nicht mehr ganz so mystifiziert ist.

Habe ich irgendwo Quatsch geschrieben? Oder hast Du eine Frage zum Thema, die hier nicht abgedeckt ist? Dann schreibt mir gerne einen Kommentar! Ich versuche es dann hier zu beantworten oder mach daraus einen eigenen Artikel, wenn ich das Thema spannend finde. Danke schon mal dafür!

"1" height="1" alt="">

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.