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.


Deprecated: Directive 'allow_url_include' is deprecated in Unknown on line 0