Vom Kleinen aufs Große schließen

Jede Operation in MySQL (wie überall anders auch) kostet etwas Zeit. In der Praxis ist die Antwortzeit eines der wichtigsten Kriterien für Software. Um so mehr sollte man schauen, dass man die Abfragen optimiert, wo es nur geht.

Wenn die Tabellen ihrer Datenbank normalisiert sind, benötigt man für eine Abfrage oft Joins. Oft benötigt man allerdings nicht alle Datensätze einer Tabelle sondern schränkt die Ergebnismenge über LIMIT ein. Wie sollte nun vorgegangen werden, damit eine solche Operation möglichst fix geht?

Vieles kann man über Indizes lösen, allerdings gibt es manchmal einen einfacheren Weg: Wir gehen einfach so vor, wie ein Mensch vorgehen würde beim „Joinen“.

Beispielauftrag: Finden sie die 10 Artikel aus unserem Shop, die am häufigsten angeklickt wurden. Holen sie neben dem Namen des Artikels auch ein Artikelbild (zu jedem Artikel kann es 0 bis unendlich geben) und die Kategorie (ein Artikel hat genau eine Kategorie). Wir haben also 3 Tabellen: Artikel, Bilder und Kategorien.

Man könnte nun diese Query anwenden:

SELECT name,bildID,kategoriename 
FROM artikel 
INNER JOIN kategorien ON artikel.kategorien_ID=kategorien.ID 
LEFT JOIN bilder ON artikel.ID=bilder.artikel_ID 
GROUP BY artikel.ID 
ORDER BY klicks DESC LIMIT 10

Der Left JOIN ist nötig, da wir auch Artikel haben, die kein Bild haben und die müssen ja auch berücksichtigt werden. Das GROUP BY ist wichtig, da wir Artikel mit mehreren Bildern haben, wir aber nur einen Datensatz pro Artikel erhalten wollen (und nicht den gleichen Artikel mit unterschiedlichen Bildern).
Was muss das DBMS nun tun? Es muss alle Datensätze der einzelnen Tabellen laut den Bedingungen verknüpfen zu einer temporären Zwischentabelle. Anschließend werden alle Datensätze nach Klicks sortiert und nach der Anzeigen-ID gruppiert. Und erst dann werden die 10 Artikel genommen mit den meisten Klicks. Die restlichen Tausenden Einträge, die mühsam gejoint und sortiert wurden, sind überflüssig. Sollte man unnötige Dinge machen lassen?? Natürlich nicht.

Wie also würde ein Mensch vorgehen, wenn er diese 3 Tabellen vor sich hätte? Er würde sich zuerst die 10 Artikel mit den meisten Klicks suchen und anschließend nur zu diesen 10 Kategorie und Bilder joinen.

SELECT name,bildID,kategoriename FROM 
(SELECT name,ID,kategorien_ID
FROM artikel  
ORDER BY klicks DESC LIMIT 10) t
INNER JOIN kategorien ON t.kategorien_ID=kategorien.ID 
LEFT JOIN bilder ON t.ID=bilder.artikel_ID 
GROUP BY t.ID
ORDER BY NULL

Nun wird das gleiche Ergebnis ausgegeben, aber in meinem Beispiel (ich hatte noch eine zusätzliche Where-Bedingung) hat es eine Beschleunigung von ca 5 Sekunden auf 0,1 Sekunden gebracht. Das ORDER BY NULL ist übrigens nötig, damit MySQL die Reihenfolge des Ergebnisses der Subquery nicht mehr verwirft, sondern so beibehält, die die Datensätze aus der Subquery zurückkommen.

Wichtig zu erwähnen ist sicherlich noch, dass für diese Art der Optimierung der Einsatz von Subqueries möglich sein muss. Das ist bei MySQL ab Version 4 der Fall.

Jan hat 152 Beiträge geschrieben

Ein Kommentar zu “Vom Kleinen aufs Große schließen

  1. Christoph sagt:

    Sehr guter Tipp, macht sich nur auf MySQL-Systemen, die Subqueries nicht unterstützen, etwas … schlecht 😉 Sollte vielleicht im Beitrag erwähnt werden.

Eine Antwort schreiben

Ihre E-Mail-Adresse wird nicht veröffentlicht. Benötigte Felder sind markiert mit *

You may use these HTML tags and attributes: <a href=""> <blockquote cite=""> <pre lang=""> <b> <strong> <i> <em>