Zum Inhalt springen
Core Web Vitals Spezialisten
Alle Artikel Server-Performance

Datenbank-Query-Optimierung für die Shop-Performance

14 Min. Lesezeit
DatenbankQuery-OptimierungIndexeTTFB

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.

Query-Wasserfall: N+1 ungeindext vs. indiziert und gecachtDatenbankzeit einer Kategorieseite mit 20 Produktenvorher: N+1 ohne IndexSELECT Liste 18 ms+ 20x SELECT je ProduktFull Scan ohne IndexDB-Zeit gesamt 420 msnachher: Eager Load + IndexSELECT Liste 9 ms1x JOIN Produkte (Index) 6 msQuery-Cache HIT 0,3 msDB-Zeit gesamt 15 msQuery-Zeit vorher und nachhervorher420 msnachher15 ms0,8 sguter TTFB-Wert (web.dev)1%Umsatz je 100 ms (Amazon)8,4%mehr Conversion je 0,1 s32%mehr Absprung 1 zu 3 sEin guter TTFB liegt bei 0,8 Sekunden oder darunter (web.dev) -- jede zusätzliche 100 ms Latenz kostete Amazon rund 1 Prozent Umsatz (Amazon).Eine um 0,1 Sekunden schnellere mobile Ladezeit steigerte Retail-Conversions um 8,4 Prozent (Google/Deloitte 2020).Beispielwerte zur Veranschaulichung des N+1-Musters; reale Zeiten variieren je nach Datenbestand und Hardware.

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

Die TTFB ist die Zeit bis zum ersten Antwort-Byte und enthält die serverseitige Verarbeitung samt Datenbankzeit. Eine Slow Query ist eine Abfrage, die einen definierten Zeitschwellenwert überschreitet -- sie landet im Slow-Query-Log. EXPLAIN ist ein Datenbankbefehl, der den geplanten Ausführungsweg einer Abfrage zeigt: ob ein Index genutzt wird, wie viele Zeilen geprüft werden und ob ein teurer Full Table Scan stattfindet.

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.sql
-- 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) priorisieren

Beim 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-plus-1-vs-eager.sql
-- 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

Häufig entsteht N+1 nicht in der Geschäftslogik, sondern erst beim Rendern: Eine Schleife im Template greift pro Element auf eine Beziehung zu, die noch nicht geladen ist, und löst je Durchlauf eine Abfrage aus. Solche Stellen fallen im normalen Test mit wenigen Datensätzen kaum auf -- erst mit realem Datenvolumen explodiert die Abfragezahl. Ein Query-Zähler je Request macht das Problem zuverlässig sichtbar.

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.

explain-beispiel.sql
-- 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 niedrig

Drei 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

EXPLAIN und Slow-Query-Log liefern nur dann verlässliche Aussagen, wenn sie an einem realistischen Datenbestand laufen. Eine Abfrage, die auf 500 Testprodukten schnell ist, kann auf 500.000 echten Produkten einbrechen, weil ein Full Table Scan dann erst spürbar wird. Optimieren Sie deshalb gegen eine Datenmenge, die der Produktion nahekommt -- sonst optimieren Sie für ein Problem, das im Live-Betrieb ganz anders aussieht.

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

Ein Query-Cache ist nur so gut wie seine Invalidierung. Ändert sich ein Preis oder ein Lagerbestand, muss der zugehörige Cache-Eintrag entwertet werden, sonst sehen Nutzer veraltete Daten. Cachen Sie deshalb gezielt jene Abfragen, die sich selten ändern, und definieren Sie für jede gecachte Abfrage einen klaren Auslöser, der den Eintrag bei Datenänderung erneuert. Personalisierte oder bestandskritische Abfragen gehören nur mit sehr kurzer oder gar keiner Cache-Dauer in diese Schicht.

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.

ProblemSymptomDiagnoseMaßnahme
Full Table ScanEinzelne Abfrage langsamEXPLAIN zeigt type=ALLPassenden Index anlegen
N+1-AbfragenViele kleine Abfragen je SeiteQuery-Zähler, Slow-Query-LogEager Loading per JOIN/IN
Wiederholte LastGleiche Abfrage sehr oftHäufigkeit im LogQuery-Caching mit Invalidierung
Falscher IndexSchreibvorgänge langsamUngenutzte Indexe prüfenÜberflüssige Indexe entfernen
Großer DatenbestandWächst mit der Zeitrows-Wert in EXPLAINIndexe 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.

Projekterfahrung aus 50+ Performance-Projekten

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

Wer langsame Abfragen mit dem Slow-Query-Log findet, N+1-Muster durch Eager Loading auflöst, Full Table Scans mit gezielten Indexen vermeidet und wiederkehrende Last per Query-Caching abfängt, senkt die Datenbankzeit -- und damit die TTFB -- an der Wurzel statt sie nur zu überdecken.

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.

Dieser Artikel basiert auf Daten aus: web.dev (Time to First Byte und Schwellenwerte), Amazon (Latenz und Umsatz), Google/SOASTA Research (Mobile Page Speed Benchmarks), Google/Deloitte (Milliseconds Make Millions) und Google (Suchgeschwindigkeit sowie Mobile Page Speed und Abbruchrate). Alle genannten Statistiken wurden zum Zeitpunkt der Veröffentlichung geprüft. Die Beispielwerte im Mockup dienen der Veranschaulichung des N+1-Musters und sind keine Messdaten.