Datenbank-Query-Optimierung für die Shop-Performance
Ein Shop wirkt oft langsam, lange bevor das erste Pixel erscheint -- und die Ursache liegt häufig in der Datenbank. Bevor der Server überhaupt HTML ausliefern kann, muss er Produkte, Preise, Kategorien und Lagerbestände abfragen. Hängt diese Abfrage, hängt die ganze Seite. Diese Wartezeit landet direkt in der Time to First Byte (TTFB), und web.dev nennt einen Wert von 0,8 Sekunden (web.dev) oder darunter als gut, während über 1,8 Sekunden (web.dev) als schlecht gelten. Die TTFB ist kein abstrakter Laborwert: Amazon stellte fest, dass jede zusätzliche 100 Millisekunden (Amazon) Latenz rund 1 Prozent (Amazon) Umsatz kosteten. Dieser Beitrag zeigt, wie Sie langsame Abfragen mit EXPLAIN sichtbar machen, das berüchtigte N+1-Problem auflösen, mit Indexen Full Table Scans vermeiden und mit Query-Caching wiederkehrende Last abfangen -- abgegrenzt vom reinen Seiten-Caching, das eine andere Schicht bedient. Wer die Datenbankzeit senkt, senkt die TTFB an der Wurzel, statt sie nur zu kaschieren.
Warum die Datenbank die TTFB bestimmt
Die Time to First Byte misst die Zeit von der Anfrage bis zum ersten empfangenen Byte der Antwort. Bei serverseitig gerenderten Shops steckt in dieser Spanne die gesamte Server-Verarbeitung -- und ein erheblicher Teil davon ist Datenbankzeit. Eine Kategorieseite löst schnell dutzende Abfragen aus: Produktliste, Varianten, Preise je Kundengruppe, Lagerbestände, Bewertungen. Jede einzelne kostet Zeit, und sie summieren sich, weil sie meist sequenziell ausgeführt werden. Solange diese Abfragen laufen, wartet der Browser auf das erste Byte -- und der Nutzer sieht eine leere Seite. Wie wenig Geduld dabei bleibt, zeigt eine Google-Messung: Schon 400 Millisekunden (Google) längere Suchergebniszeiten senkten die Zahl der Suchanfragen um 0,6 Prozent (Google).
Die TTFB ist selbst keine Core-Web-Vitals-Metrik, aber sie geht jedem sichtbaren Render voraus. Sie verschiebt First Contentful Paint und Largest Contentful Paint nach hinten, weil das Markup erst nach Abschluss der Datenbankarbeit fließt. Eine fundierte Server-Optimierung betrachtet deshalb nicht nur Webserver und Netzwerk, sondern die Abfragen selbst. Denn der schnellste Webserver bringt wenig, wenn er auf eine träge Query wartet, die mit dem richtigen Index in Millisekunden statt Hundertstelsekunden antworten könnte.
TTFB, Slow Query und EXPLAIN -- die Begriffe
Slow Queries sichtbar machen mit dem Slow-Query-Log
Optimierung ohne Messung ist Raten. Der erste Schritt ist deshalb stets, die langsamen Abfragen überhaupt zu finden. Datenbanken wie MariaDB und MySQL bieten dafür ein Slow-Query-Log: Es protokolliert jede Abfrage, die einen einstellbaren Zeitschwellenwert überschreitet. So wird aus dem vagen Gefühl, der Shop sei langsam, eine konkrete Liste von Abfragen mit Laufzeit, Häufigkeit und betroffenen Tabellen. Wer ohne dieses Log optimiert, arbeitet im Blindflug -- und investiert Zeit womöglich in Abfragen, die selten laufen.
-- Slow-Query-Log aktivieren und Schwelle auf 0,2 Sekunden setzen
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.2;
-- Auch Abfragen ohne Index protokollieren (besonders aufschlussreich)
SET GLOBAL log_queries_not_using_indexes = 'ON';
-- Danach: das Log periodisch auswerten und nach
-- Gesamtzeit (Laufzeit x Haeufigkeit) priorisierenBeim Auswerten zählt nicht nur die Einzellaufzeit, sondern die Gesamtzeit aus Laufzeit mal Häufigkeit. Eine Abfrage, die einzeln nur 30 Millisekunden braucht, aber pro Seitenaufruf zwanzigmal läuft, kostet mehr als eine seltene Abfrage von 200 Millisekunden. Genau dieses Muster -- viele kleine, wiederholte Abfragen -- ist der Übergang zum N+1-Problem, dem häufigsten und tückischsten Performance-Killer in Shops. Eine systematische Performance-Analyse verbindet das Slow-Query-Log mit der TTFB-Messung und zeigt, welche Abfragen die Antwortzeit real dominieren.
Das N+1-Problem: viele kleine Abfragen statt einer
Das N+1-Problem entsteht, wenn eine Anwendung zuerst eine Liste lädt (1 Abfrage) und dann für jeden Eintrag der Liste eine weitere Abfrage absetzt (N Abfragen). Eine Kategorieseite mit 20 Produkten lädt also nicht 1, sondern 21 Abfragen -- und mit jeder zusätzlichen Beziehung, etwa dem Hersteller oder der Bewertung je Produkt, vervielfacht sich die Zahl. Jede dieser Abfragen ist für sich harmlos, doch die schiere Menge erzeugt Latenz, Verbindungsaufbau und Verarbeitungs-Overhead, der die Datenbankzeit in die Höhe treibt.
Das Tückische am N+1-Problem ist seine Unsichtbarkeit im Code: Eine harmlos aussehende Schleife über Produkte löst im Hintergrund hunderte Abfragen aus, ohne dass eine einzelne davon im Slow-Query-Log auffällt. Erst die Summe schmerzt. Die Lösung heißt Eager Loading: Statt N einzelner Abfragen lädt man die zugehörigen Daten in einer einzigen, gebündelten Abfrage vor -- typischerweise über einen JOIN oder ein vorgezogenes Sammel-SELECT mit IN-Klausel.
-- N+1: erst die Liste, dann pro Produkt eine eigene Abfrage
SELECT id, name FROM products WHERE category_id = 42; -- 1 Abfrage
SELECT * FROM prices WHERE product_id = 1001; -- + N Abfragen
SELECT * FROM prices WHERE product_id = 1002;
-- ... noch 18 weitere
-- Eager Loading: alles in einer gebuendelten Abfrage
SELECT p.id, p.name, pr.amount
FROM products p
JOIN prices pr ON pr.product_id = p.id
WHERE p.category_id = 42;N+1 versteckt sich gern in Templates und Listen
Indexe: Full Table Scans vermeiden
Selbst eine einzelne Abfrage kann langsam sein, wenn die Datenbank für jede Zeile der Tabelle prüfen muss, ob sie zur Bedingung passt -- ein Full Table Scan. Bei wenigen tausend Zeilen fällt das kaum auf, bei einem gewachsenen Produktkatalog mit hunderttausenden Datensätzen wird es zur Bremse. Ein Index funktioniert wie das Register eines Buchs: Statt jede Seite durchzublättern, springt die Datenbank direkt zur passenden Stelle. Indizierte Abfragen prüfen oft nur eine Handvoll Zeilen statt der gesamten Tabelle. Da web.dev einen TTFB über 1,8 Sekunden (web.dev) als schlecht einstuft, entscheidet genau dieser Unterschied oft darüber, ob eine Seite den guten Bereich noch erreicht.
Die Kunst liegt darin, die richtigen Spalten zu indizieren -- typischerweise jene, nach denen gefiltert (WHERE), sortiert (ORDER BY) oder verknüpft (JOIN) wird. Besonders wirksam sind zusammengesetzte Indexe, die mehrere Spalten in der richtigen Reihenfolge abdecken, etwa Kategorie und Status zusammen. Indexe sind allerdings kein Selbstzweck: Jeder Index muss bei jedem Schreibvorgang mitgepflegt werden und kostet Speicher. Zu viele oder schlecht gewählte Indexe verlangsamen Inserts und Updates, ohne Lesevorteile zu bringen. Es gilt, gezielt nach Bedarf zu indizieren, nicht pauschal.
WHERE-Spalten
Spalten, nach denen gefiltert wird, gehören indiziert. Ohne Index muss die Datenbank jede Zeile prüfen; mit Index springt sie direkt zu den passenden Treffern.
JOIN- und Sortier-Spalten
Verknüpfungs- und ORDER-BY-Spalten profitieren stark von Indexen. Sie vermeiden teure Sortier- und Abgleich-Operationen über die komplette Tabelle.
Zusammengesetzte Indexe
Mehrere Spalten in der richtigen Reihenfolge decken kombinierte Filter ab. Die Spaltenreihenfolge entscheidet, welche Abfragen den Index nutzen können.
EXPLAIN: den Ausführungsplan lesen
Ob eine Abfrage einen Index nutzt oder doch über die ganze Tabelle scannt, verrät der Befehl EXPLAIN. Er stellt den Ausführungsplan voran: Welche Tabellen werden in welcher Reihenfolge gelesen, welcher Index kommt zum Einsatz, und wie viele Zeilen schätzt die Datenbank zu prüfen. EXPLAIN ist damit das wichtigste Diagnosewerkzeug der Query-Optimierung -- es macht aus Vermutungen belegbare Entscheidungen. Wer einen Index setzt, sollte vorher und nachher den EXPLAIN-Plan vergleichen, um die Wirkung zu bestätigen.
-- Plan der Abfrage anzeigen
EXPLAIN SELECT id, name FROM products
WHERE category_id = 42 AND active = 1;
-- Warnsignale in der Ausgabe:
-- type = ALL -> Full Table Scan (kein Index genutzt)
-- key = NULL -> kein Index ausgewaehlt
-- rows = sehr hoch -> viele Zeilen werden geprueft
-- Ziel: type = ref/range, key gesetzt, rows niedrigDrei Felder verdienen besondere Aufmerksamkeit. Der Wert type = ALL signalisiert einen Full Table Scan -- die Datenbank liest die komplette Tabelle, weil kein passender Index existiert. Ein leeres key-Feld bestätigt, dass kein Index gewählt wurde. Und ein hoher rows-Wert zeigt, wie viele Zeilen die Datenbank voraussichtlich prüft. Sinkt dieser Wert nach dem Anlegen eines Index drastisch, war der Index goldrichtig. So wird die Optimierung nachvollziehbar statt zufällig.
Praxistipp: an realem Datenvolumen testen
Query-Caching: wiederkehrende Last abfangen
Manche Abfragen lassen sich beschleunigen, andere am besten ganz vermeiden. Viele Daten in einem Shop ändern sich selten -- die Kategoriestruktur, Hersteller, Produktstammdaten -, werden aber bei jedem Seitenaufruf erneut abgefragt. Query-Caching legt das Ergebnis einer Abfrage in einem schnellen Speicher ab, sodass identische Folgeanfragen ohne erneute Datenbankarbeit beantwortet werden. Ein Treffer im Cache antwortet in einem Bruchteil der Zeit, die eine echte Abfrage benötigt -- und entlastet zugleich die Datenbank für die Abfragen, die wirklich frische Daten brauchen. Der wirtschaftliche Hebel ist beträchtlich: Im Reise-Segment stieg die Conversion sogar um 10,1 Prozent (Google/Deloitte, 2020) je 0,1 Sekunden schnellerer mobiler Ladezeit.
Entscheidend ist die richtige Schicht. Anwendungsseitiges Caching häufig benötigter Ergebnisse in einem In-Memory-Speicher ist meist wirksamer als das datenbankinterne Caching, weil es nicht nur das Abfrageergebnis, sondern den ganzen Verarbeitungsweg überspringt. Wie sich solche Caches mit Werkzeugen wie Redis und einem Reverse-Proxy wie Varnish kombinieren lassen, vertieft unser Beitrag zu Caching-Strategien mit Varnish und Redis -- die Datenbank-Optimierung und das Caching greifen hier sauber ineinander.
Cache-Invalidierung nicht vergessen
Query-Optimierung im Shop-Alltag
Im E-Commerce zeigen sich die größten Hebel an wiederkehrenden Stellen: der Startseite mit ihren Teasern, der Kategorieseite mit Filtern und Sortierung, der Produktseite mit Varianten und Bewertungen sowie dem Checkout mit Bestands- und Preisprüfung. Gerade Filter- und Sortierabfragen über große Kataloge profitieren stark von passenden Indexen, während Listendarstellungen klassische N+1-Kandidaten sind. Die Reihenfolge der Optimierung folgt dabei der Gesamtzeit aus dem Slow-Query-Log: zuerst die Abfragen, die in Summe am meisten kosten. Dass sich der Aufwand lohnt, zeigt sich am Verhalten: Schnellere Seiten halten Nutzer länger, und im Handel gaben Besucher bei schnelleren Seiten rund 9,2 Prozent (Google/Deloitte, 2020) mehr aus.
Bei serverseitig gerenderten Shops auf Basis von Shopware CE lohnt es, die häufigsten Seitentypen einzeln zu betrachten und je Seite die Abfragezahl zu messen. Sinkt sie von einigen hundert auf wenige Dutzend, ist das meist auf aufgelöste N+1-Muster zurückzuführen. Welche weiteren Hebel im Shop-Kontext greifen, vertieft unsere Seite zur Shopware-Performance. Die Datenbank-Optimierung bildet dort das Fundament: Ohne schnelle Abfragen bleibt auch das beste Frontend-Tuning Stückwerk.
| Problem | Symptom | Diagnose | Maßnahme |
|---|---|---|---|
| Full Table Scan | Einzelne Abfrage langsam | EXPLAIN zeigt type=ALL | Passenden Index anlegen |
| N+1-Abfragen | Viele kleine Abfragen je Seite | Query-Zähler, Slow-Query-Log | Eager Loading per JOIN/IN |
| Wiederholte Last | Gleiche Abfrage sehr oft | Häufigkeit im Log | Query-Caching mit Invalidierung |
| Falscher Index | Schreibvorgänge langsam | Ungenutzte Indexe prüfen | Überflüssige Indexe entfernen |
| Großer Datenbestand | Wächst mit der Zeit | rows-Wert in EXPLAIN | Indexe und Paginierung |
Die schnellste Abfrage ist die, die gar nicht erst gestellt wird. Erst Eager Loading räumt mit unnötigen N+1-Abfragen auf, dann sorgen Indexe dafür, dass die verbleibenden Abfragen wenige statt aller Zeilen prüfen -- und Query-Caching fängt ab, was sich ohnehin selten ändert.
Messen, absichern und dauerhaft schnell bleiben
Query-Optimierung ist kein einmaliges Projekt, sondern ein Dauerthema, weil Datenbestände wachsen und neue Features neue Abfragen mitbringen. Eine Abfrage, die heute schnell ist, kann mit dem zehnfachen Katalog morgen zur Bremse werden. Deshalb gehört das Slow-Query-Log dauerhaft aktiviert und regelmäßig ausgewertet, idealerweise verbunden mit einer TTFB-Überwachung je Seitentyp. So werden Regressionen sichtbar, bevor Nutzer sie spüren. Das ist auch betriebswirtschaftlich relevant: Die Wahrscheinlichkeit eines Absprungs steigt bei einer Ladezeit von einer auf drei Sekunden um 32 Prozent (Google/SOASTA, 2017).
Wir kombinieren die Auswertung von Slow-Query-Logs und EXPLAIN-Plänen mit einer kontinuierlichen TTFB-Messung, um die Datenbankzeit von der reinen Netzwerklatenz zu trennen. Erst diese Trennung zeigt, ob eine hohe TTFB von langsamen Abfragen oder von der Distanz zum Server stammt. Da 53 Prozent (Google, 2018) der mobilen Besuche abgebrochen werden, wenn das Laden länger als drei Sekunden dauert, ist diese Beobachtung kein Luxus, sondern Teil unserer Performance-Leistungen, die Datenbank, Server und Frontend gemeinsam betrachten.
Der Kern in einem Satz
Der Aufwand zahlt sich mehrfach aus. Eine niedrige TTFB verbessert First Contentful Paint und Largest Contentful Paint und damit die Core Web Vitals, was die organische Sichtbarkeit stärkt. Schnellere Abfragen entlasten die Datenbank, senken die Server-Last und stabilisieren die Antwortzeiten auch unter Lastspitzen. Und sie wirken direkt auf den Umsatz: Eine um 0,1 Sekunden schnellere mobile Ladezeit steigerte Retail-Conversions um 8,4 Prozent (Google/Deloitte, 2020). Eng verwandt sind dabei zwei weitere Hebel: die mobile Performance-Optimierung, die zeigt, wie sich die schnellere TTFB auf mobilen Verbindungen auszahlt, sowie die gezielte Steuerung des Ladevorgangs mit Resource Hints wie Preload und Prefetch, die nach der Datenbankarbeit ansetzen. Den Grundstein legt jedoch stets die Server-Optimierung, die die Datenbankzeit als Erstes adressiert.