30 November 2011

Foreign Keys in MySQL lösen keine Triggers aus

Es kann einen Entwickler schon manchmal an den Rand der Verzweiflung bringen, wenn Triggers bzw. Stored Procedures nicht funktionieren und dann noch die Ursache nicht im eigenen Code zu finden ist. Das beste Beispiel ist das Problem, dass wenn ein Foreign Key sich per Constraint ändert, kein Trigger ausgelöst wird.

Eine Constraint könnte folgendermassen aussehen:

ALTER TABLE `contact`
	ADD CONSTRAINT `constraint_contact_contact_salutation`
	FOREIGN KEY (`contact_salutation`)
	REFERENCES `salutation` (`salutation_id`)
	ON DELETE SET NULL ON UPDATE CASCADE;

Die Constraint besagt, wenn eine Anrede in der Tabelle “salutation” gelöscht wird, dass alle Verknüpfungen in “salutation_id” in der Tabelle “contact” auf NULL zurückgesetzt werden sollen, um die referentielle Integrität zu wahren. Wurde jedoch auf die Tabelle “contact” folgender Trigger definiert

CREATE TRIGGER `contact_delete` AFTER DELETE ON `contact`
	FOR EACH ROW BEGIN
		-- some Code
	END;
//

wird dieser nicht ausgeführt. Das Problem ist den Entwicklern bei MySQL bereits seit über 6 Jahren bekannt, aber eine Lösung oder ein Fix ist nicht in Sicht.

http://bugs.mysql.com/bug.php?id=11472

Eine Workaround wäre ein Trigger in der Tabelle “salutation” anzulegen, was die Aufgaben selbst durchführt. In diesem Beispiel kein größeres Problem, aber es gibt weitaus kompliziertere Verstrickungen von Tabellen, wo ein Workaround geradezu unmöglich ist. Wird bspw. eine Spalte einer Tabelle in 10 weiteren referenziert, steht man vor einem unlösbaren Problem, deren Lösung vielleicht nur in einem Wechsel zu einer anderen Datenbank zu finden wäre.

24 November 2011

Langsame Subselects bei großen InnoDB Tabellen in MySQL

Langsame Subselects bei großen InnoDB Tabellen in MySQL

Mit Anwendung von Subselects bei InnoDB Tabellen mit mehreren tausend Datensätzen geht die Performance von MySQL massivst in den Keller. Bereits einfache Abfragen können den Datenbankserver zum Erliegen bringen, auch wenn eine Zugriff über Schlüssel oder Indizes erfolgt:

mysql> SELECT `contact_id` FROM `contact` WHERE `contact_id` = 1;
1 row in set (0.00 sec)      -- FAST
mysql> SELECT `contact_id` FROM `contact` WHERE `contact_id` IN
-> ( SELECT `contact_id` FROM `contact` WHERE `contact_id` = 1 );
1 row in set (20.68 sec)     -- SLOW

Der Fehler ist dem Entwicklerteam bereits bekannt und behoben, jedoch mit dem Vermerk “Closed in MySQL 6.0″.

http://bugs.mysql.com/bug.php?id=9021

Die Version 6 von MySQL läßt jedoch auf sich warten und wird in nächster Zeit nicht für den produktiven Einsatz zur Verfügung stehen. Solange könnnen nur Workarounds wie bspw. über JOINs weiterhelfen:

SELECT
    `contact`.`contact_id`
FROM
    `contact`
INNER JOIN (
    SELECT
        `contact_id`
    FROM
        `contact`
    WHERE
        `contact_id` = 1
) `subselect`
ON
    `subselect`.`contact_id` = `contact`.`contact_id`

20 November 2011

MySQL Optimierung – Ein kleiner Tipp

Welcher Webmaster hat nicht das Problem, dass die MySQL Datenbank zu lange auf der CPU hängt und den Server in die Knie zwingt.

SHOW PROCESSLIST bringt es an das Licht, welche Query die meiste Zeit beansprucht. Anschließend können ermittelte SELECT-Anweisungen beginnend mit EXPLAIN untersucht werden, um wichtige Informationen zur Laufzeit ermitteln zu können.

EXPLAIN SELECT id FROM table ...

Langsame Abfragen sind bspw. durch fehlende oder “fehlerhafte” Indizes, langsame Joins bei n:n Beziehungen (kartesisches Produkt) sowie temporäre Tabellen, welche automatisch erzeugt werden, zu erkennen.

Weitere wichtige Hintergrundinformationen sind in der EXPLAIN Dokumentation von MySQL zu finden.

15 November 2011

HTTP 304 ist nicht alles – Advanced Caching mit mod_expires

Mit HTTP 304 kann die Bandbreite und der Webserver erheblich geschont werden, jedoch fällt hier immer noch ein Request an, welcher vom Webserver verarbeitet werden muss. Die billigste Lösung wäre, wenn erst gar keine Anfrage vom Client erfolgen würde, weil diese die Daten bereits hat.

Jeder Browser legt automatisch jede Datei im lokalen Cache ab, jedoch wird eine Datei ein zweites mal benötigt, wird der Server kontaktiert und ermittelt, ob die Datei sich verändert hat. Die Datei wird erst aus dem Cache verwendet, wenn der Server ein “HTTP 304 Not Modified” liefert. Im anderen Fall wird diese vom Server erneut geladen.

Ob ein Browser die Anfrage startet, hängt davon ab, ob im HTTP Header der jeweiligen Datei Cache Informationen vorhanden sind. Mit PHP lässt sich das ziemlich einfach steuern. Im folgenden Beispiel verfällt die Datei quasi im Cache des Browser nicht.

\header( 'Expires: Thu, 01 Jan 2034 01:00:00 GMT' );

Bei dynamischen Content, die bspw. mit PHP erzeugt werden, gestalltet sich die Sache etwas schwierig und sollte genaustens überlegt sein. Bei statischen Content wie Bildern kann hier leicht das Apache Module “mod_expires” verwendet werden, welche die Last auf dem Server erheblich mindert.

ExpiresActive  On
ExpiresDefault "access plus 2 minutes"
ExpiresByType  image/png  "access plus 2 days"
ExpiresByType  text/html  "modification plus 5 minutes"

Die Dokumentation von “mod_expires” befindet sich unter:

http://httpd.apache.org/docs/2.2/mod/mod_expires.html

13 November 2011

HTTP 200 ist gut, aber HTTP 304 ist auch nicht schlecht!

Wer möchte nicht ständig die Performance seiner Webapplikationen verbessern. Jedoch sollte man nicht nur ein Augenmerk für die Leistung der Anwendung haben, sondern sich auch der Bandbreitenoptimierung widmen. Geringe Bandbreite bedeutet nämlich auch einen schnelleren Seitenaufbau für den User.

HTTP 304 ist nicht wirklich ein Fehler, sondern gibt an, dass sich eine Ressource seit dem letzten Zugriff nicht geändert hat. HTTP 304 schickt dem Client nur Headerinformation ohne HTML im Body und das spart erheblich Bandbreite. Es macht nämlich keinen Sinn Dokumente wiederholt dem Client zu übertragen, wenn er diese bereits hat und nur wissen wollte, ob diese sich geändert haben. Hierfür schickt der Client dem Server If_Modified_Since Headers.

Ein Webserver unterstützt diese Methode bereits, aber jedoch nur für statische Dokumente wie Bilder oder reine HTML-Dateien. Bei Webanwendungen wie bspw. bei PHP ist der Entwickler gefragt. Ein Anwendungsszenario wäre bspw. das dynamische Generieren von Bildern, aber auch für generiertes HTML und JavaScript.

Ein vereinfachtes Beispiel mit PHP könnte folgendermaßen aussehen:

$headers = @\apache_request_headers();
if (
	isset( $headers['If-Modified-Since'] )
	&& \strtotime( $headers['If-Modified-Since'] ) == \filemtime( __FILE__ )
) {
	\header(
		'Last-Modified: ' . \gmdate(
			D, d M Y H:i:s',
			\filemtime( __FILE__ )
		) . ' GMT',
		true, 304
	);
	exit;
}

Natürlich müsste filemtime( __FILE__ ) für das jeweilige Szenario angepasst werden, bspw. der Dateiname einer gecachten Datei oder auch der Timestamp aus einer Datenbank. Auch sollte eine Prüfung stattfinden, ob tatsächlich bei dynamischen Seiten derselbe Inhalt vom Client gefordert wird.