Das Putzlowitsch Test- und SEO-Blog

GROUP BY auf ein Unique Field kann durchaus sinnvoll sein

Kürzlich hatte ich einen Beitrag zu dem Problem mit WordPress und der Sortierreihenfolge bei MySQL 5.0.51 (wie es derzeit bei Strato verwendet wird) geschrieben und mich dort über das vermeintlich unsinnige GROUP BY mit einem eindeutigen Feld (ID) ausgelassen. Für eine einfache SQL-Abfrage, wie dort beim ersten Beispiel, ist das auch durchaus richtig, da ist das „GROUP BY id“ tatsächlich sinnfrei.

Bei WordPress ab Version 2.1 wurde das dann auch geändert, hier wird dieses GROUP BY mit der ID nur noch dann eingefügt, wenn es tatsächlich benötigt wird, nämlich wenn man die Abfrage auf bestimmte Kategorien oder Tags einschränken will. Das könnte z.B. etwa so aussehen:

<?php  query_posts( $query_string.'&cat=1,2,3' ); ?>

Hier will man zum Beispiel nur Artikel anzeigen, die in Kategorie 1, 2 oder 3 enthalten sind. Wenn nun ein Artikel mehreren Kategorien zugeordnet ist, würde das dazu führen, daß dieser Artikel auch mehrmals in der Liste steht und das will man ja nicht. Genau hier bewirkt das GROUP BY id, daß jeder Artikel nur einmal auftaucht. Man hätte das zwar ebenso mit DISTINCT erreicht, aber WP macht es halt mit GROUP BY. Letztendlich ergibt sich daraus ein Problem, welches aber einer fehlerhaften Implementierung von MySQL 5.0.51 zuzuschreiben ist.

Da ich bei mir nicht mit Kategorien oder Tags in den Abfragen arbeite, war mir das bisher auch nicht klar. Erst ein Beitrag im wordpress.org Forum hat mir die Augen geöffnet (Dank an Otto42 für die erhellenden Worte :-).

Was bedeutet das nun für die Praxis? Ganz klar, das Problem kann nur durch ein Update von MySQL behoben werden, da ist dann der Webhoster in der Pflicht. Nun ist es aber nicht ganz einfach, goße Hoster wie Strato dazu zu bewegen, zumal es MySQL 5.0.53 wohl auch noch nicht gibt.
In der Zwischenzeit kann erstmal mein Plugin helfen, welches das GROUP BY id in ein GROUP BY post_date „verbiegt“. Einen kleinen Nachteil hat das aber. Wenn es zwei oder mehrere Artikel mit auf die Sekunde übereinstimmendem Veröffentlichungszeitpunkt gibt, wird nur einer von diesen dargestellt.

Keine Kommentare »

WordPress und die suboptimale MySQL-Optimierung (5.0.51)

Problem

Es ist ja lobenswert, daß Strato relativ schnell die MySQL-Versionen aktualisiert, vor ein paar Tagen von 5.0.45 auf 5.0.51. Man sollte auch denken, daß es dadurch mit Applikationen keine Probleme geben dürfte. Nich so diesmal, denn plötzlich stimmte bei so manchen WordPressinstalltionen die Sortierreihenfolge der Beiträge nicht mehr, wie mehrere Nutzer im WP-Deutschlanforum beklagten. Aber nicht nur Strato war davon betroffen, auch bei 1&1 und anderen Hostern gab es Probleme.

Normalerweise kommen ja die neuesten Artikel zuerst, es wird also absteigen nach Datum sortiert. Nun waren aber plötzlich die ältesten Beiträge ganz oben, als scheinbar eine umgekehrte, also aufsteigende Sortierung. Und niemand hatte bewußt etwas verändert, also keine Plugins installiert oder die Themedateien bzw. die Konfiguration angepaßt. Also konnte es nur am Hoster liegen. Da ich hier bei Schnurpsel diesen Effekt nicht beobachten konnte, dachte ich zunächst doch an irgendein Plugin, bis ich mal spaßeshalber mein WordPress-2.0.x-Testbolg aufgrufen hatte. Und siehe da, auch hier waren die Beiträge plötzlich nicht mehr in der gewünschten Reihenfolge. Da ich dort aber wirklich seit Monaten nichts verändert hatte und nur ein paar eigene Plugins verwende, mußte es doch irgendeine andere Ursache haben. Zumindest hatte ich jetzt die Chance, der Sache auf den Grund zu gehen.

Hintergrund

Meine erster Schritt war, mir mal die von WP generierte SQL-Abfrage anzusehen. Meine Vermutung war zunächst, daß irgendwo die Sortierung (ORDER BY post_date) verloren geht. So sieht die SQL-Abfrage aus:

SELECT DISTINCT *
 FROM wp20_posts
 WHERE 1 =1
 AND post_date_gmt <= '2008-01-11 22:03:59'
 AND (
  post_status = 'publish'
  OR post_author =1
  AND post_status != 'draft'
  AND post_status != 'static' 
 )
 AND post_status != 'attachment'
 GROUP BY wp20_posts.ID
 ORDER BY post_date DESC
 LIMIT 0 , 10

Vom komischen WHERE 1=1 mal abgesehen gab es zumindest keine Auffälligkeiten, die Sortierung ist auch drin aber was soll bitte das GROUP BY wp20_posts.ID? Das Feld ID ist ein Autoinkrement-Feld, also immer eindeutig (UNIQUE) und somit kann es da nie zwei- oder mehrmals den selben Wert geben, also kann auch nichts gruppiert werden. Mit GROUP BY feldname werden normalerweise Datensätze zusammengefaßt, die in dem oder den angegebenen Feldern die selben Werte enthalten. Ein Blick in den Quelltext gibt dann Aufschluß, dieses GROUP BY ist beim Zusammensetzen des SQL-Strings fest codiert und muß daher immer belegt werden, damit die Abfrage syntaktisch richtig ist:

"SELECT $distinct * FROM $wpdb->posts $join WHERE 1=1" . $where . " GROUP BY " . $groupby . " ORDER BY " . $orderby . " $limits";

Wenn keine echte Gruppierung verwendet wird, wird dafür einfach als Dummy-Wert das Feld ID eingesetzt. Normalerweise kein Probem, weil ja eben dadurch nichts gruppiert wird. Und genau deshalb wird sowas auch vom SQL-Server letztendlich wegoptimiert, daß heißt, es steht zwar da, kommt aber nicht zur Anwendung. Außerdem bewirkt ein GROUP BY auch implizit eine Sortierung nach den dort angegebenen Feldern. Und genau hier liegt das Problem.

Wurde bei MySQL vor Version 5.0.51 mit dem GROUP BY für eindeutige Felder auch die implizite Sortierung wegoptimiert, ist das in der neuen Version nun anders. Hier bleit diese Sortierung erhalten, auch wenn keine Gruppierung stattfindet. In den Release-Notes findet man den entsprechenden Hinweis zu diesem Fehler (30596).

Lösung

Betroffen ist, sofern nicht durch Plugins Gruppierungen hinzugefügt oder verändert werden, nur WordPress Version 2.0.x, zu WP 1.x kann ich nichts sagen. Bei WordPress ab Version 2.1 wurde das Verhalten geändert, so daß GROUP BY nur noch dann in den SQL-String eingebaut wird, wenn wirklich eine Gruppierung verwendet werden soll.
Nachtrag: Auch in WordPress ab Version 2.1 kann das Problem auftreten, wenn man etwas mit Kategorien oder Tags (ab 2.3) verändert, also z.B. nur bestimmte Kategorien in die Anzeige einbeziehen will:

<?php query_posts( $query_string.'&cat=1,2,3' ); ?>

Falls man keine spezielle Kategorie- oder Tagkonfiguration verwendet, ergibt sich auch direkt die erste Lösungsmöglichkeit, nämlich auf die neuste WordPress-Version (derzeit 2.3.2) zu aktualisieren.
Falls man das aus irgendeinem Grund nicht möchte oder das Kategorie-/Tagproblem auftritt, ist die zweite Lösung ein Plugin, welches das GROUP BY auf einen nicht störenden Wert zurechtbiegt.
Als WordPress-Plugin sehen die paar Zeilen PHP-Code dann so aus:

<?php
/*
Plugin Name: 123 No Group By ID
Plugin URI: http://schnurpsel.de/wordpress-und-die-suboptimale-mysql-optimierung-5051-74/
Description: Ändert bei WP das GROUP BY id in GROUP BY post_date (Problem ab MySQL 5.0.51).
Author: Ingo Henze
Version: 0.12
Author URI: http://putzlowitsch.de/
*/ 

	// GROUP BY auswerten
	function plw123ngb_posts_groupby( $groupby ) {
		if( preg_match( "/(|[ ,.])id(|[ ,])/i", $groupby ) ) {
			// sonst GROUP BY auf post_date setzen	
			$groupby = 'post_date';
		}
		return $groupby;
	}

	add_filter( 'posts_groupby', 'plw123ngb_posts_groupby' );
?>

Mann kann den Quelltext hier einfach rauskopieren, in einer Datei speichern, auf den Server in das Pluginverzeichnis kopieren und aktivieren. Oder man nimmt das fertige Plugin als ZIP-Datei.

Download: 123 No Group By ID 0.12

Sofern in der "GROUP BY"-Felderliste das Feld ID auftaucht, wird alles durch POST_DATE ersetzt. Das ist zwar so ein bißchen eine "Holzhammermethode", sollte aber in den meisten Fällen keine Nebenwirkungen zeigen. Probleme könnten nur dann auftreten, falls ein Plugin auch irgendwelche Gruppierungen vornimmt und dabei ebenfalls das Feld ID mit einbezieht. Zudem würde für den eher unwahrscheinlichen Fall, daß es zwei oder mehrere Beiträge mit exakt dem selben Veröffentlichungszeitpunkt gibt, nur einer von diesen angezeigt werden.

42 Kommentare »

WordPress 2.3 – Problem ohne www bei Strato

Hintergrund

Es war mir ja schon fast am Anfang meiner Strato-Zeit aufgefallen. Die Servervariable HTTP_HOST liefert immer den Hostname mit einem vorangestellten www zurück, selbst dann, wenn der Aufruf ohne www erfolgte. Eigentlich eine eigenwillige, wenn nicht gar falsche Konfiguration des Strato-Severs, denn die Variable HTTP_HOST soll eigentlich genau das enthalten, was der Browser im HTTP-Requestheader übermittelt. Wenn ich also die Seite schnurpsel.de aufrufe, dann sollte eben das da drin stehen, und nicht etwa www.schnurpsel.de. Aber genau das passiert bei Strato (PowerWeb A und S).

Problem

Bisher stellte das auch kein Problem dar, mit dem Neuen WordPress 2.3 aber schon (deshalb war meine Seite gestern Abend auch zeitweise nicht erreichbar). Ab WordPress 2.3 ist eine Funktion integriert, die ein sogenanntes „Canonical Redirect“ ausführt. Auf das Strato-Problem bezogen bedeutet das, daß eine Weiterleitung z.B. dann stattfindet, wenn der Aufruf der Seite nicht so erfolgt, wie das in der WordPresskonfiguration eingestellt ist.
Wenn z.B in WordPress als Blog-Adresse (URL)

http://schnurpsel.de

eingetragen ist, jemand aber die Seite mit

http://www.schnurpsel.de

aufruft, wird er auf

http://schnurpsel.de

umgeleitet (und anders rum). An sich eine sinnvolle Sache, gerade unter SEO-Aspekten.

Was passiert nun aber bei Strato? Die funktion redirect_canonical schaut in HTTP_HOST nach, wie die Seite aufgerufen wurde, stellt fest, daß da ein www steht, aber das Blog ohne www konfiguriert ist. Also wird flux auf die Adresse ohne www weitergeleitet (diese Information bekommt der Browser zurück). Der ruft dann brav die Seite erneut ohne www auf, weil aber Strato das www wieder vorneran stellt, leitet redirect_canonical erneut um, der Browser ruft wieder brav ohne www auf usw. usw. Es ensteht also eine Weiterleitungs-Endlosschleife, die der Browser dann aber (hoffentlich) nach ein paar Versuchen mit einer Meldung beenden sollt. Beim Firefox sieht das dann so aus:

Fehler: Umleitungsfehler
Die aufgerufene Website leitet die Anfrage so um, dass sie nie beendet werden kann.

Das alles tritt aber nur beim Blog selber auf, der Adminbereich ist davon nicht betroffen.

Lösung

Aber auch hier gibt es mit einem bißchen Basteln Abhilfe. Glücklicherweise gibt es in den Servervariablen einen Eintrag, der die tatsächlich aufgerufene Adresse enthält, nämlich SCRIPT_URI. Dieser enthält die vollständigen Aufrufadresse mit http und gegebenfalls Verzeichnis und Dateiname, z.B.

http://schnurpsel.de

Hier kann man einfach den echten Hostanamen extrahieren und der Variable HTTP_HOST zuweisen. Und schon ist die Welt wieder in Ordnung :-)

Also WordPress-Plugin sehen die paar Zeilen PHP-Code dann so aus:

<?php
/*
Plugin Name: 123 True HTTP Host
Plugin URI: http://schnurpsel.de/wordpress-23-problem-ohne-www-bei-strato-65/
Description: Setzt den wahren HTTP_HOST aus dem Request. Bei Strato wird immer www davor geschrieben.
Author: Ingo Henze
Version: 0.10
Author URI: http://putzlowitsch.de/
*/ 
$plw123thh = parse_url( $_SERVER['SCRIPT_URI'] );
if( isset( $plw123thh['host'] ) && ($plw123thh['host']!=$_SERVER['HTTP_HOST']) )
  $_SERVER['HTTP_HOST'] = $plw123thh['host'];
?>

Mann kann den Quelltext hier einfach rauskopieren, in einer Datei speichern,auf den Server in das Pluginverzeichnis kopieren und aktivieren. Oder man nimmt das fertige Plugin als ZIP-Datei.

Download: 123 True HTTP Host 0.10

Im Übrigen tritt das Problem mit der Weiterleitungs-Endlosschleife bei einer Konfiguration mit www nicht auf, allerdings funktioniert dann das redirect_canonical auch nicht, weil die Seiten ja scheinbar immer korrekt mit www aufgerufen werde.

116 Kommentare »

WordPress 2.3 – Plugins oder my-hacks bei der Installation aktivieren

Bereits seit WordPress 2.1 gibt es ein vermutlich eher unbekanntes Feature, mit dem man bereits bei der Installation eines neuen, nackten WordPress benutzerdefinierte Aktionen ausführen kann. Schlüssel dazu ist eine Datei install.php, welche sich im Verzeichnis wp-content befinden muß. Ist also bei der Installation die Datei wp-content/install.php vorhanden, wird deren Inhalt noch vor den WP-eigenen Installtionsfunktionen geladen und kann damit z.B. die wp_install ersetzen.

Am einfachtesten ist es, sich die gewünschte Funktion aus der Datei wp-admin/include/updrade.php (früher wp-admin/upgrade-functions.php ) zu kopieren, die notwendigen Änderungen vorzunehmen und das dann in wp-content/install.php zu speichern.

Und wofür ist das nun gut?
Ich hatte ja zum Problem WP 2.3 Plugincheck geschrieben, das es bei der Neuinstallation auf Grund des Henne-Ei-Problems normalerweise nicht möglich ist, das Plagin zu aktivieren, ohne mindestens einmal die Pluginseite aufgerufen zu haben und damit bereits die Daten an wordpress.org zu senden. Das ist nun mit so einer benutzerdefinierten install.php möglich.

Download: Benutzerdefinierte install.php (als ZIP)

Diese Datei enthält nichts weiter, als die originale WP-Installationsfunktion, am Ende durch folgende Programmzeilen ergänzt:

// Automatische aktiviere von Plugins oder der my-hacks.php
// Plugins
$active_plugins = get_option( 'active_plugins' );
$active_plugins[] = 'plw123_anon_vchek.php';  // hier den Plugin-Dateiname eintragen
update_option( 'active_plugins', $active_plugins );
// my-hacks.php
// update_option( 'hack_file', 1 );

Das mit der my-hacks.php ist auskommentiert, es soll nur das Prinzip veranschaulichen.

Wie sieht nun also eine WordPress 2.3 Neuinstallation aus:

  • Wie gewohnt alle WordPressdateien auf den Server übertragen (wp-config.php nicht vergessen)
  • zusätzlich das Plugin in das Pluginverzeichnis kopieren
  • zusätzlich die benutzerdefinierte install.php in das Verzeichnis wp-content kopieren
  • WordPressinstallation wie gewohnt starten

Fertig!

Viel Spaß mit dem anonymisierten, neuen WordPress 2.3 :-)

Nachtrag: Hannes hat auch ein Plugin geschrieben, welches zusätzlich noch die Pluginliste entschlackt. Sein Plugin kann man natürlich auch mit der beschriebenen Methode gleich bei der Installation aktivieren. Dazu muß einfach nur der Name geändert werden, also an Stelle von:

$active_plugins[] = 'plw123_anon_vchek.php';  // hier den Plugin-Dateiname eintragen

entsprechend

$active_plugins[] = 'anonymous-plugin-updates.php';  // hier den Plugin-Dateiname eintragen

Und es muß auch in das Pluginverzeichnis kopiert werden, das ist ja klar.

2 Kommentare »

WP 2.3 – Anonym up-to-date bleiben – Plugin

Vorgestern hatte ich meinen Hack gegen die Übertragung einiger Daten (Blog-URL, aktive Plugins) beim neuen WP 2.3 Versionscheck vorgestellt. Nun scheint es aber eine gewisse Unsicherheit bei oder Abneigung gegen die Verwendung der, von WP selbst als veraltete bezeichneten, my-hacks.php zu geben. Deshalb habe ich das Ganze hier noch mal als Plugin verpackt.
Man bedenke aber, das man zum Aktivieren des Plugins im Backend die Plugin-Seite aufrufen muß und dadurch mindestens einmal alle Daten ungefiltert an api.wordpress.org übertragen werden.

Wenn man sein System von einer älteren WordPress-Version updatet, ist das kein Probelm. Dann kann man das Plugin einfach vor dem Update installieren und aktivieren. Bei einer Neuinstalltion müßte man aber, bevor man irgendwas anderes macht, das Plugin direkt in der Datenbank aktivieren, was ein bißchen fummelig ist. Zudem setzt man die Filterung auch außer Kraft, wenn man z.B. mit der Funktion „Deaktivier alle Plugins“ alle Plugins ausschaltet.

NEU: Auch die Pluginversion kann bei einer Neuinstallation verwendet!

Wie auch immer, hier nun das „123 Anonymer Versionscheck“-Plugin.

Download: 123 Anonymer Versionscheck 0.10

Die Funktionsweise entspricht exakt der Hack-Version. Weitere Informationen bitte dort nachlesen.

Weitere Plugins zum Thema

  • Hannes ersetzt in seinem Plugin die Updatefunktion mit einer angepaßten eigenen Version und reduziert dort auch die übertragene Pluginliste auf die notwendigen Daten.
  • Filosofo’s Tinfoil-Hat Plugin bringt gleich ein komplett neues Updatesystem mit, doktert also nicht nur an den Symptomen rum, sondern mach gleich „Nägel mit Köpfen“.

Diese Plugins lassen sich auch mit dieser Methode bereits bei der Neuinstallation aktivieren.

8 Kommentare »