MySQL – To JOIN or not to JOIN

Als PHP Entwickler ist man ständig damit beschäftigt etwas aus einer Datenbank zu laden. Dabei kommen auch die ominösen Joins zum Einsatz, von dem alle immer erzählen sie wären böse. In diesem Artikel will ich mich genau mit diesem Vorurteil beschäftigen.

Problemstellung

Es geht bei der Diskussion um Joins generell um die Frage der Performance. Für die Praxis relevant wird es also erst bei größeren Datenmengen (Tabellen mit mehr als 1.000 Zeilen) oder sehr komplizierten Abfragen. Dazu kommt natürlich, dass sich die gewünschten Daten über mehrere Tabellen verteilen.

Für den weiteren Verlauf nehmen wir folgendes Beispiel:

Wir wollen eine Liste aller Logeinträge ab einem bestimmten Datum und möchten dazu die Namen der Benutzer anzeigen.


Lösungsmöglichkeiten

Abruf der Daten wenn sie benötigt werden

Die vermutlich schlechteste Methode das Problem zu lösen. Hier wird pro Logeintrag eine Datenbankabfrage nach dem Benutzernamen abgefeuert. Dass das nicht gut für die Performance sein kann, ist jedem klar und so wie oben wird das auch niemand praktizieren – hoffe ich zumindest.
Allerdings kann man sehr leicht in diese Falle tappen, wenn die Datenbank hinter einem ORM abstrahiert ist.

Ups! Sieht zwar unverdächtig aus, ist aber genau das selbe was oben steht, nur durch die Abstraktion verschleiert. Die meisten ORMs werden zwar den einzelnen Benutzer intern cachen und somit bei sich wiederholenden Benutzern etwas weniger Abfragen abschicken, aber viel besser wird es damit auch nicht.

Ein Request mit einem JOIN

Die selbe Sache sieht mit Join dann so aus:

Die Verknüpfung der Einträge übernimmt jetzt die Datenbank für uns. Im Vergleich zu oben haben wir hier einen massiven Performance-Gewinn. Wichtig bei Joins ist aber immer, sie mit einen EXPLAIN zu überprüfen, nicht dass die Join-Bedingungen ohne Index arbeiten.

Prepared Statement und Stored Procedure

Einige SQL-Dialekte lassen noch weitere Möglichkeiten zu unser Problem zu lösen. So könnten wir auch mit einem Prepared Statement den Benutzernamen laden.

Bevor die Datenbank überhaupt nach Einträgen sucht, muss unser Query erst compiliert und optimiert werden. Das ist für so ein einfaches Query eine Menge Überhang, der sich durch ein Prepared Statement sparen lässt. Wir sind hier mit der Performance also zwischen den ersten beiden Varianten. Da entscheidet dann wie üblich die Praxis. 100 Einträge aus der Logtabelle -> Alles OK. 100.000 Einträge aus der Logtabelle -> Problem.

Mit einer Stored Procedure verhält es sich genauso. Vom Code her gibt es nicht viel zu sehen, aber der Vollständigkeit halber hier:

Fazit

„Joins sind böse“ ist bei weitem zu pauschal. Joins sind durchaus die beste Lösung für bestimmte Problemstellungen. Die Aussage sollte also vielmehr als Denkanstoß betrachtet werden. Auch zur Frage: Warum sind die Daten überhaupt auf mehrere Tabellen verteilt? Würden wir in unserem Beispiel den Benutzernamen ins Log übernehmen, wäre das Problem auch gelöst – aber die Datenbank nicht mehr in Normalform.