Datenbank-Optimierung für schnellere Webanwendungen
Langsame Seiten liegen selten am Server, sondern an einer Abfrage, die zu viel tut. Wie Sie die teuren Abfragen finden, Indizes richtig setzen und N+1-Muster erkennen.
Langsame Webanwendungen liegen selten an fehlender Rechenleistung, sondern an einer Handvoll Abfragen, die zu viele Daten anfassen. Der Weg ist immer derselbe: zuerst die teuren Abfragen messen, dann gezielte Indizes setzen, dann N+1-Muster auflösen — und erst danach über größere Hardware nachdenken.
Wenn eine Webanwendung langsam wird, wird zuerst über größere Server gesprochen. In den allermeisten Projekten liegt die Zeit aber nicht in der Rechenleistung, sondern in einer Handvoll Abfragen, die mehr Daten anfassen als nötig — und die man erst sieht, wenn man sie misst.
Warum zuerst messen und nicht raten?
Jede ernstzunehmende Datenbank kann sagen, welche Abfragen die meiste Zeit verbrauchen: PostgreSQL über pg_stat_statements, MySQL über das Slow-Query-Log. Sortiert nach Gesamtzeit statt nach Einzeldauer ergibt sich fast immer dasselbe Bild — zwei bis fünf Abfragen erklären den größten Teil der Last. Alles, was danach kommt, ist Feinarbeit.
Zur Einordnung gehört der Ausführungsplan: EXPLAIN ANALYZE zeigt, ob ein Index benutzt wird oder ob die Datenbank die ganze Tabelle liest. Ein sequenzieller Scan über 200 Zeilen ist harmlos, über zwei Millionen ist er die Ursache.
Wie viele Indizes braucht eine Tabelle?
- ›Index auf die Spalten, nach denen tatsächlich gefiltert und sortiert wird — nicht auf jede Spalte, die in einer Abfrage vorkommt.
- ›Zusammengesetzte Indizes in der Reihenfolge der Bedingungen anlegen; die erste Spalte entscheidet über die Nutzbarkeit.
- ›Jeder Index kostet Schreibzeit und Speicher: bei schreibintensiven Tabellen ist ein Index weniger oft schneller als einer mehr.
- ›Ungenutzte Indizes regelmäßig aufspüren und entfernen — sie bremsen jeden INSERT, ohne je gelesen zu werden.
Woran erkennen Sie das N+1-Muster?
Der häufigste Performancefehler in modernen Anwendungen stammt nicht aus SQL, sondern aus dem Code darüber: Eine Liste mit 50 Einträgen wird geladen, und für jeden Eintrag folgt eine weitere Abfrage — 51 statt einer. Im Entwicklungsdatensatz mit zehn Zeilen fällt das nicht auf, in Produktion mit tausenden schon. Erkennbar ist es an einer Abfrage, die im Log hunderte Male mit unterschiedlichen Parametern auftaucht.
Die Lösung ist immer dieselbe: die benötigten Daten in einer Abfrage zusammenholen, statt sie in der Schleife nachzuladen. Ob das über einen Join, ein IN mit gesammelten IDs oder das Eager Loading des ORM geschieht, ist Geschmackssache — die Anzahl der Rundreisen zur Datenbank ist es nicht.
Was lohnt sich zwischenzuspeichern?
Wenn eine Auswertung sich stündlich ändert, aber sekündlich abgefragt wird, ist Caching der größere Hebel als jede Indexoptimierung. Sinnvoll ist eine klare Regel, wann der Zwischenspeicher ungültig wird — beim Schreiben des zugrundeliegenden Datensatzes, nicht nach Gefühl. Ohne diese Regel entsteht die zweite Fehlerklasse: schnelle Seiten mit falschen Zahlen.
Messen, die teuersten drei Abfragen ansehen, N+1 beseitigen, dann Indizes setzen, dann cachen. Wer mit dem größeren Server beginnt, kauft Zeit zum vierfachen Preis — und dieselbe Abfrage bleibt langsam.
Wann ist wirklich die Datenbank das Problem?
Bevor optimiert wird, lohnt eine Zuordnung der Zeit: Wie viel der Antwortzeit entsteht in der Datenbank, wie viel im Anwendungscode, wie viel im Netzwerk? Ein einfacher Zeitstempel um den Datenbankaufruf herum beantwortet das in einer Stunde. Liegen 40 Millisekunden in der Datenbank und 900 im Rendern, ist jede Indexdiskussion vergeudete Zeit.
Ebenso wichtig ist die Datenmenge in der Entwicklungsumgebung. Wer gegen 200 Zeilen entwickelt und gegen zwei Millionen ausliefert, wird jedes Skalierungsproblem erst in Produktion sehen. Ein anonymisierter Auszug mit realistischer Größenordnung ist die günstigste Vorsorge, die es gibt.
Was kosten Verbindungen und Wartezeiten?
Bei Lastspitzen ist häufig nicht die Abfrage langsam, sondern die Wartezeit auf eine freie Verbindung. Ein Verbindungspool mit einer Obergrenze, die zur Datenbank passt, verhindert, dass hunderte parallele Anfragen sich gegenseitig blockieren. Und lange Transaktionen gehören kurz gehalten: Wer eine Transaktion über einen HTTP-Aufruf zu einem Drittsystem offen lässt, hält Sperren, während er wartet.
Wir bauen das für Sie
Vom Artikel zur Implementierung — sprechen Sie mit uns.