Aktuelle Beiträge Zum Blog

Ein Shop wird selten von einem Tag auf den anderen langsam. Er wird es schrittweise: Der Katalog wächst, Filter kommen dazu, ein Auswertungsbericht im Backend zieht Zahlen über zwölf Monate, und ein Zusatzfeld bekommt keinen Index. Irgendwann braucht die Kategorieseite eine Sekunde länger als im Vorjahr, und niemand kann sagen, woran es liegt. Dieser Beitrag zeigt, wie Sie die Datenbank dazu bringen, es Ihnen selbst zu sagen: mit dem Slow-Query-Log, einer belastbaren Auswertung und Indizes, die zur tatsächlichen Abfrage passen. Unsere Shopware-Agentur arbeitet genau diese Reihenfolge ab, wenn ein Shop ohne erkennbaren Anlass träge wird.

Warum die Datenbank im Verborgenen bremst

Die Datenbank ist die Schicht im Shop, die am seltensten eine sichtbare Fehlermeldung erzeugt. Ein Template mit einem Syntaxfehler bricht ab, ein fehlendes Bild hinterlässt eine Lücke, ein abgelaufenes Zertifikat blockiert den Aufruf. Eine Abfrage dagegen liefert auch dann ein korrektes Ergebnis, wenn sie dafür 1,8 Sekunden und zwei Millionen gelesene Zeilen benötigt. Sie ist nicht kaputt, sie ist nur teuer. Genau deshalb wandert dieses Problem durch alle Ausbaustufen eines Shops mit, ohne dass es jemand aufschreibt. Wer sein Hosting und die Wartung fachlich betreuen lässt, bekommt die Kurve dieser Kosten früher zu sehen als über Beschwerden aus dem Kundenservice.

Der wirtschaftliche Rahmen dahinter ist stabil: 4 Prozent der Internetnutzerinnen und -nutzer in Deutschland kaufen täglich im Netz ein, weitere 28 Prozent mindestens einmal pro Woche (Bitkom). Diese Gruppe kennt Ihren Shop bereits. Sie kommt mit einer Erwartung an Tempo, die sich aus dem gesamten Onlinehandel speist und nicht aus Ihrer Serverkonfiguration. Eine Kategorieseite, die zwei Sekunden auf die Datenbank wartet, fällt bei einem wöchentlichen Besucher deutlicher auf als bei jemandem, der zweimal im Jahr bestellt.

Wie schnell technische Störungen zum Kaufabbruch führen, hat Bitkom schon 2017 erhoben: Als Gründe für abgebrochene Bestellungen nannten 49 Prozent eine schlechte Internetverbindung, 43 Prozent Schwierigkeiten beim Bezahlvorgang, 42 Prozent eine wenig nutzerfreundliche Webseite und 32 Prozent einen Fehler auf der Webseite (Bitkom 2017). Die Zahlen stammen aus einer Befragung von 2017 und beschreiben die Rangfolge, nicht den heutigen Stand. Bemerkenswert ist die Reihenfolge trotzdem: Vier der meistgenannten Gründe sind technischer Natur, und drei davon liegen im Zugriffsbereich des Shopbetreibers.

Wo die Datenbank im Ladeprofil sitzt

Die Zeit bis zum ersten Byte ist der Teil der Ladezeit, in dem der Server rechnet - und damit der Teil, in dem eine langsame Abfrage direkt sichtbar wird. Als gut gelten Werte von 0,8 Sekunden oder weniger, als schlecht Werte über 1,8 Sekunden (web.dev). Im Feld erreichen 44 Prozent der mobilen Aufrufe einen guten Wert, am Schreibtisch sind es 55 Prozent (HTTP Archive Web Almanac 2025). Wer diesen Wert dauerhaft beobachten lassen möchte, findet den Aufbau in unserem Beitrag zum Monitoring von Verfügbarkeit und Performance.

Das Slow-Query-Log einschalten

Der erste Schritt kostet nichts und wird trotzdem übersprungen: Das Protokoll langsamer Abfragen ist ab Werk abgeschaltet. In der Dokumentation steht das in einem einzigen Satz - das Slow-Query-Log ist standardmäßig deaktiviert (MariaDB). Ohne diesen Schalter gibt es keine Datengrundlage, und jede Diskussion über Indizes bleibt eine Meinungsfrage. Wenn Sie ohnehin gerade eine Versionsumstellung planen, lohnt der Blick in unseren Beitrag zum End of Life von MySQL 8.0 - Protokollierung und Migration lassen sich in einem Wartungsfenster erledigen.

Der zweite Punkt ist der Schwellwert. Standardmäßig wird eine Abfrage erst protokolliert, wenn sie länger als eine bestimmte Zeit braucht, voreingestellt sind zehn Sekunden (MariaDB). Für einen Shop ist das unbrauchbar: Zehn Sekunden erreicht eine einzelne Abfrage im Normalbetrieb kaum, während sich das eigentliche Problem aus vierhundert Abfragen zu je 40 Millisekunden zusammensetzt. Der kleinste zulässige und der voreingestellte Wert von long_query_time sind 0 beziehungsweise 10 (MySQL), und der Wert lässt sich mit einer Auflösung von Mikrosekunden angeben (MySQL). Praktisch bewährt hat sich ein Einstieg bei 0,2 Sekunden mit späterer Absenkung.

my.cnf
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 0.2
log_queries_not_using_indexes = 1
min_examined_row_limit = 1000
log_slow_rate_limit = 1
log_slow_verbosity = query_plan,explain

Der Schalter log_queries_not_using_indexes erweitert das Protokoll um eine zweite Klasse von Kandidaten. Das Slow-Query-Log lässt sich so einstellen, dass Abfragen ohne Indexnutzung unabhängig von ihrer Ausführungszeit protokolliert werden, entweder über die Option not_using_index in log_slow_filter oder durch Setzen der Systemvariablen log_queries_not_using_indexes auf 1 (MariaDB). Das ist der Schalter, der die schnellen, aber strukturell falschen Abfragen sichtbar macht - jene, die heute in 30 Millisekunden durch 50.000 Zeilen laufen und in achtzehn Monaten durch 900.000.

Damit dieser zweite Schalter das Protokoll nicht flutet, gehört eine Untergrenze dazu. Es kann sinnvoll sein, Abfragen aus dem Protokoll auszuschließen, die weniger als eine Mindestzahl von Zeilen untersuchen (MariaDB). Eine Abfrage, die zehn Zeilen ohne Index liest, ist kein Fall für eine Optimierung, sondern ein Nachschlagen in einer Konfigurationstabelle. Mit min_examined_row_limit legen Sie fest, ab welcher Größenordnung Sie überhaupt hinsehen wollen.

  • slow_query_log - der Hauptschalter. Ohne ihn wird nichts geschrieben.
  • long_query_time - die Schwelle in Sekunden, Nachkommastellen bis in den Mikrosekundenbereich sind zulässig.
  • log_queries_not_using_indexes - protokolliert Abfragen ohne Indexnutzung unabhängig von der Dauer.
  • min_examined_row_limit - hebt die Bagatellgrenze an, damit kleine Nachschlagevorgänge draußen bleiben.
  • log_slow_rate_limit - schreibt nur jede n-te passende Abfrage mit, wenn das Protokoll zu schnell wächst.
  • log_slow_verbosity - ergänzt Abfrageplan und Erklärung, sodass die Auswertung ohne Nachstellen auskommt.

Auf einem Shop mit hohem Aufkommen kann selbst ein sauber eingegrenztes Protokoll zu groß werden. Dafür gibt es eine Drosselung: Das Slow-Query-Log lässt sich über die Systemvariable log_slow_rate_limit drosseln (MariaDB). Statt jeder passenden Abfrage wird dann nur jede zehnte oder hundertste geschrieben. Für eine Rangliste nach Häufigkeit reicht das aus, solange Sie die Stichprobe bei der Auswertung mitrechnen.

Vor dem Einschalten an den Platz denken

Ein Protokoll bei 0,2 Sekunden Schwelle und aktiviertem Indexfilter kann auf einem gut besuchten Shop mehrere Gigabyte pro Tag erreichen. Legen Sie die Datei auf eine Partition mit Reserve, richten Sie eine Rotation ein und setzen Sie sich ein Enddatum für den Messlauf. Wie ein solches Zeitfenster in einen geplanten Ablauf passt, beschreibt unser Beitrag zu Lasttest und Notfallplan in der Hochsaison.

Vom Rohprotokoll zur Rangliste

Ein Slow-Query-Log ist im Rohzustand eine Textdatei mit Tausenden fast gleicher Einträge. Wer sie von Hand liest, findet die auffälligste Abfrage und übersieht die teuerste. Für die Zusammenfassung liefert der Serverhersteller ein Werkzeug mit: Um das zu erleichtern, können Sie den Befehl mysqldumpslow verwenden, um eine Slow-Query-Log-Datei zu verarbeiten und ihren Inhalt zusammenzufassen (MySQL). Das Werkzeug ersetzt Literale durch Platzhalter und gruppiert damit alle Varianten derselben Abfrage zu einer Zeile.

Terminal
$ mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
Count: 18422 Time=0.31s (5710s) Lock=0.00s (0s) Rows=1.0 (18422) SELECT id, price FROM product_price WHERE product_id = N AND rule_id = N
$ mysqldumpslow -s c -t 10 /var/log/mysql/slow.log
Count: 96140 Time=0.04s (3845s) Lock=0.00s (0s) Rows=12.0 (1153680) SELECT * FROM product_category WHERE category_id = N ORDER BY position ASC

Die entscheidende Spalte ist nicht die Einzeldauer, sondern das Produkt aus Dauer und Anzahl - im Beispiel oben die Zahl in Klammern hinter der Zeit. Eine Abfrage mit 0,04 Sekunden wirkt harmlos, verbraucht bei 96.140 Aufrufen am Tag aber mehr Rechenzeit als die spektakuläre Abfrage mit 0,31 Sekunden. Wer nur nach der längsten Laufzeit sortiert, optimiert die falsche Stelle und wundert sich, warum die Ladezeit gleich bleibt.

  1. Nach Gesamtzeit sortieren (-s t), nicht nach Spitzenwert - das zeigt, wo die Rechenzeit tatsächlich hingeht.
  2. Nach Anzahl sortieren (-s c) und prüfen, ob eine Abfrage pro Seitenaufruf mehrfach ausgeführt wird.
  3. Das Verhältnis von gelesenen zu zurückgegebenen Zeilen ansehen: 1.153.680 gelesene für 96.140 Aufrufe ist ein anderer Befund als 12 gelesene je Aufruf.
  4. Die drei teuersten Muster herausschreiben und einzeln weiterverfolgen, statt die Liste von oben nach unten abzuarbeiten.
Die Summe entscheidet, nicht der Ausreißer

Eine Abfrage, die zehnmal am Tag 1,2 Sekunden braucht, kostet zwölf Sekunden. Eine Abfrage, die 90.000-mal am Tag 40 Millisekunden braucht, kostet eine Stunde. Beide stehen im selben Protokoll, aber nur eine davon verändert die Ladezeit spürbar. Die Details zu Caching und Seitenaufbau stehen im Beitrag zur Shopware-6-Performance-Optimierung.

EXPLAIN schätzt, ANALYZE misst

Sobald eine teure Abfrage feststeht, folgt der Blick auf den Ausführungsplan. EXPLAIN zeigt, welchen Weg der Optimierer wählen würde: welche Tabelle zuerst gelesen wird, welcher Index infrage kommt, wie viele Zeilen er erwartet. Der wichtigste Vorbehalt steckt im Wort erwartet. Die Spalte rows ist eine Schätzung aus der Statistik der Tabelle, keine Messung. Und sie beschreibt die Zeilen, die der Plan zu lesen gedenkt, bevor ein LIMIT greift - eine Abfrage mit LIMIT 20 kann in dieser Spalte eine sechsstellige Zahl tragen und trotzdem nach zwanzig Treffern abbrechen. Umgekehrt kann eine niedrige Schätzung eine teure Abfrage verbergen, wenn die Statistik veraltet ist.

Beide verbreiteten Serverzweige bieten deshalb eine messende Variante an. In der einen Ausprägung führt EXPLAIN ANALYZE eine Anweisung aus und erzeugt eine Ausgabe wie EXPLAIN zusammen mit Zeitangaben und zusätzlichen, iteratorbasierten Informationen darüber, wie die Erwartungen des Optimierers zur tatsächlichen Ausführung passten (MySQL). Damit steht neben jeder geschätzten Zahl der gemessene Wert. Wer solche Läufe in die Entwicklungsumgebung einbaut, findet den passenden Rahmen in unserer Programmierung und Entwicklung.

In der anderen Ausprägung heißt der Befehl schlicht ANALYZE: Die Anweisung ruft den Optimierer auf, führt die Anweisung aus und erzeugt dann eine EXPLAIN-Ausgabe anstelle der Ergebnismenge (MariaDB). Der Unterschied zur Schätzung steckt in zwei zusätzlichen Spalten. r_rows ist ein beobachtungsbasiertes Gegenstück zur Spalte rows und zeigt, wie viele Zeilen tatsächlich aus der Tabelle gelesen wurden (MariaDB). Wo rows 1.200 sagt und r_rows 486.000 zeigt, ist die Statistik das Problem und nicht der Index.

diagnose.sql
-- Schätzung: was der Optimierer erwartet
EXPLAIN
SELECT p.id, p.product_number
FROM `order` o
JOIN order_line_item p ON p.order_id = o.id
WHERE o.order_date_time >= '2026-08-01'
  AND o.state_id = 0x9F2C
ORDER BY o.order_date_time DESC
LIMIT 20;

-- Messung: was tatsächlich passiert ist
ANALYZE
SELECT p.id, p.product_number
FROM `order` o
JOIN order_line_item p ON p.order_id = o.id
WHERE o.order_date_time >= '2026-08-01'
  AND o.state_id = 0x9F2C
ORDER BY o.order_date_time DESC
LIMIT 20;

Diese beiden Spalten stehen nicht nur im interaktiven Aufruf zur Verfügung. Die Spalten r_rows und r_filtered sind auch in der EXPLAIN-Ausgabe enthalten, die ab Version 10.1.0 in das Slow-Query-Log geschrieben wird (MariaDB). Mit log_slow_verbosity auf query_plan,explain bekommen Sie also die Messung direkt im Protokoll - ohne die Abfrage nachstellen und ohne die Produktivlast künstlich nachbauen zu müssen. Das ist der Punkt, an dem eine Diagnose von Rekonstruktion auf Beobachtung umschaltet.

MerkmalEXPLAINANALYZE / EXPLAIN ANALYZE
Abfrage wird ausgeführtneinja
ZeilenzahlSchätzung aus der Statistikbeobachteter Wert
Wirkung von LIMIT sichtbareingeschränktja, über die gemessene Zeilenzahl
Zeitangaben je Schrittkeinevorhanden
Auf Produktivsystemengefahrlosführt die Abfrage wirklich aus
Im Slow-Query-Log verfügbarüber log_slow_verbosityüber log_slow_verbosity

Für die Bewertung gibt es eine brauchbare Faustregel direkt aus der Dokumentation. Zum Beispielfall heißt es dort, der Optimierer habe die Zahl der passenden Datensätze etwas überschätzt, und weiter: Wenn Sie einen vollständigen Scan haben und r_filtered unter 15 Prozent liegt, ist es an der Zeit, einen passenden Index zu erwägen (MariaDB). Diese Schwelle ist kein Naturgesetz, aber sie ersetzt das Bauchgefühl durch eine Zahl, über die sich im Team diskutieren lässt.

Das Literal im falschen Typ

Es gibt einen Fehler, der einen vorhandenen, korrekt gebauten Index vollständig ausschaltet, ohne eine Warnung zu erzeugen: das Literal im falschen Datentyp. Die Regel steht knapp in der Dokumentation: Bei Vergleichen einer Zeichenkettenspalte mit einer Zahl kann der Server keinen Index auf der Spalte verwenden, um den Wert schnell nachzuschlagen (MySQL). Der Grund ist die implizite Umwandlung: Der Server muss jede einzelne Zeile in eine Zahl umrechnen, bevor er vergleichen kann - und ein Index über die Zeichenketten hilft bei dieser Rechnung nicht.

typfalle.sql
-- customer_number ist VARCHAR(32) mit Index

-- schaltet den Index ab: Zahl gegen Zeichenkette
SELECT id FROM customer WHERE customer_number = 10042;

-- nutzt den Index: Zeichenkette gegen Zeichenkette
SELECT id FROM customer WHERE customer_number = '10042';

-- gleicher Effekt bei einer Funktion um die Spalte
SELECT id FROM `order` WHERE DATE(order_date_time) = '2026-08-01';
SELECT id FROM `order`
 WHERE order_date_time >= '2026-08-01 00:00:00'
   AND order_date_time < '2026-08-02 00:00:00';

Im Shopalltag trifft das vor allem drei Felder: Bestellnummern, Kundennummern und Artikelnummern. Alle drei sehen aus wie Zahlen, sind aber aus gutem Grund als Zeichenkette abgelegt, weil führende Nullen, Präfixe oder Buchstabenblöcke vorkommen. Sobald eine Schnittstelle den Wert als Zahl übergibt - ein Importlauf, ein Bericht, eine Anbindung an die Warenwirtschaft -, wird aus einem indizierten Nachschlagen ein vollständiger Tabellendurchlauf. Automatisierte Prüfungen fangen das früh ab; wie ein solcher Prüfrahmen aussieht, beschreibt unser Beitrag zur E2E-Testautomatisierung für Online-Shops.

Zwei Muster, die dieselbe Wirkung haben

Auch eine Funktion um die Spalte herum verhindert die Indexnutzung, weil der Index den Rohwert enthält und nicht das Ergebnis der Funktion. DATE(order_date_time) = '2026-08-01' liest die ganze Tabelle, der gleichbedeutende Bereichsvergleich mit zwei Grenzwerten nutzt den Index. Dasselbe gilt für LOWER(email) = ... und für Berechnungen wie price * 1.19 > 100. Die Regel lautet: die Spalte bleibt nackt, die Umrechnung wandert auf die andere Seite des Vergleichs.

Zusammengesetzte Indizes und ihre Reihenfolge

Die meisten Shopabfragen filtern über mehrere Spalten gleichzeitig: Verkaufskanal und Aktivstatus, Kategorie und Sortierposition, Kunde und Bestelldatum. Dafür gibt es zusammengesetzte Indizes, und ihre Kapazität ist großzügig bemessen - ein Index kann aus bis zu 16 Spalten bestehen (MySQL). Diese Zahl verleitet dazu, alle Filterspalten in einen Index zu packen. Das hilft nur, wenn die Reihenfolge stimmt, denn ein zusammengesetzter Index ist eine sortierte Liste über die Spaltenfolge und nicht eine Sammlung unabhängiger Nachschlagewerke.

Die maßgebliche Regel heißt Präfixregel und steht so in der Dokumentation: Der Server kann mehrspaltige Indizes für Abfragen nutzen, die alle Spalten des Index prüfen, oder für Abfragen, die nur die erste Spalte, die ersten beiden Spalten, die ersten drei Spalten und so weiter prüfen (MySQL). Ein Index über (sales_channel_id, active, created_at) bedient damit auch eine Abfrage, die nur nach sales_channel_id filtert. Eine Abfrage, die ausschließlich nach created_at filtert, bedient er nicht - dafür wäre ein eigener Index nötig oder eine andere Spaltenreihenfolge.

index.sql
-- Gleichheitsfilter zuerst, Bereich und Sortierung zuletzt
CREATE INDEX idx_order_channel_state_date
  ON `order` (sales_channel_id, state_id, order_date_time);

-- nutzt den Index vollständig
SELECT id FROM `order`
 WHERE sales_channel_id = 0x1A AND state_id = 0x9F
 ORDER BY order_date_time DESC LIMIT 20;

-- nutzt nur das erste Spaltenpaar
SELECT id FROM `order`
 WHERE sales_channel_id = 0x1A AND state_id = 0x9F;

-- nutzt den Index nicht: erste Spalte fehlt im Filter
SELECT id FROM `order`
 WHERE order_date_time >= '2026-08-01';
  • Spalten mit Gleichheitsvergleich gehören nach vorn, Spalten mit Bereichsvergleich dahinter - nach einem Bereich endet die nutzbare Präfixkette.
  • Die Sortierspalte gehört ans Ende, dann liest der Server die Reihenfolge aus dem Index statt sie nachträglich zu bilden.
  • Selektive Spalten vor unselektive: eine Spalte mit zwei möglichen Werten grenzt weniger ein als eine mit zehntausend.
  • Vor jedem neuen Index prüfen, ob ein bestehender ihn als Präfix bereits enthält - dann ist der neue überflüssig.
  • Bei jeder Änderung an der Spaltenfolge die betroffenen Abfragen mit einer Messung gegenprüfen, nicht mit einer Vermutung.

Zwischen Indexpflege und Zwischenspeicher gibt es dabei eine Arbeitsteilung, die sich lohnt zu kennen: Ein Zwischenspeicher senkt die Zahl der Aufrufe, ein Index senkt die Kosten je Aufruf. Beides zusammen wirkt, aber nur eins davon hilft beim ersten Aufruf nach einer Aktualisierung. Wie sich die Zwischenspeicherung im Shop einrichten lässt, steht im Beitrag zum Redis-Caching für Shopware.

Wann ein Index schadet

Ein Index ist kein kostenloses Beschleunigungsmittel. Er belegt Platz, er muss bei jedem Schreibvorgang mitgepflegt werden, und er verlängert Importläufe. Vor allem aber hilft er nicht in jeder Lage. Die Dokumentation ist an dieser Stelle ungewöhnlich deutlich: Indizes sind weniger wichtig für Abfragen auf kleinen Tabellen oder auf großen Tabellen, bei denen Auswertungsabfragen die meisten oder alle Zeilen verarbeiten (MySQL). Der Grund folgt gleich danach: Wenn eine Abfrage auf die meisten Zeilen zugreifen muss, ist sequenzielles Lesen schneller als der Umweg über einen Index (MySQL).

Praktisch heißt das: Ein nächtlicher Umsatzbericht über alle Bestellungen eines Monats braucht keinen zusätzlichen Index, sondern ein anderes Zeitfenster oder eine vorberechnete Tabelle. Ein Index auf einer Statustabelle mit zwölf Zeilen ändert nichts, weil die Tabelle ohnehin komplett im Arbeitsspeicher liegt. Wer beides trotzdem anlegt, zahlt bei jedem Schreibvorgang und gewinnt nichts.

Auch für den Grenzfall gibt es eine Zahl. Zu einem Beispiel mit einem Filter, der knapp ein Drittel der Zeilen übrig lässt, hält die Dokumentation fest: 30 Prozent sind typischerweise nicht selektiv genug, um das Anlegen neuer Indizes zu rechtfertigen (MariaDB). Zusammen mit der 15-Prozent-Marke aus dem vorigen Abschnitt ergibt sich ein brauchbarer Korridor: unter 15 Prozent lohnt ein Index in der Regel, ab 30 Prozent lohnt er sich selten, dazwischen entscheidet die Messung. Wie stark die Anwendungsschicht selbst zur Ladezeit beiträgt, zeigt der Beitrag zu OPcache und JIT unter PHP 8.5.

Index lohnt sich

Der Filter lässt einen kleinen Teil der Tabelle übrig, die Abfrage läuft oft, und r_filtered bleibt niedrig. Typisch für Produktlisten, Bestellsuchen und Kundenkonten.

Index ist neutral

Die Tabelle ist klein und liegt vollständig im Arbeitsspeicher. Der Index kostet wenig und bringt wenig - hier entscheidet die Schreiblast, nicht die Lesezeit.

Index schadet

Die Abfrage liest ohnehin fast alle Zeilen, oder die Tabelle wird im Massenimport beschrieben. Dann verlängert jeder zusätzliche Index den Lauf, ohne die Abfrage zu verkürzen.

Ein Diagnoseablauf für den Shopalltag

Die einzelnen Bausteine ergeben erst dann einen Nutzen, wenn sie in einer festen Reihenfolge abgearbeitet werden. Der folgende Ablauf lässt sich in einem Wartungsfenster starten und über zwei bis vier Wochen führen. Er endet mit einer Liste von Änderungen, die jeweils einzeln messbar sind - und nicht mit einem Paket, bei dem hinterher unklar bleibt, welcher Schritt gewirkt hat.

  1. Protokoll einschalten, Schwelle auf 0,2 Sekunden, Indexfilter an, Bagatellgrenze bei 1.000 untersuchten Zeilen.
  2. Sieben Tage laufen lassen, damit Wochenrhythmus, Newsletterversand und Importläufe im Protokoll stehen.
  3. Mit mysqldumpslow nach Gesamtzeit und nach Anzahl auswerten, die zehn teuersten Muster herausschreiben.
  4. Je Muster den Ausführungsplan messen, nicht schätzen: ANALYZE beziehungsweise EXPLAIN ANALYZE.
  5. Typfehler und Funktionen um Spalten zuerst beheben - diese Korrekturen kosten nichts und wirken sofort.
  6. Erst danach Indizes anlegen, einzeln, mit vorher und nachher gemessener Zeilenzahl und Laufzeit.
  7. Schreiblast gegenprüfen: Importlauf und Bestellabschluss nach jeder Indexänderung erneut messen.
  8. Schwelle absenken und den Lauf wiederholen, bis der teuerste Eintrag unterhalb der Relevanzgrenze liegt.

Der siebte Schritt wird am häufigsten übersprungen und am teuersten bezahlt. Ein Index, der die Kategorieseite um 400 Millisekunden beschleunigt und den nächtlichen Importlauf um zwanzig Minuten verlängert, ist ein schlechtes Geschäft, wenn der Import in ein Zeitfenster mit Filialabholung fällt. Wie eng solche Abläufe zusammenhängen, zeigt der Beitrag zu Click and Collect als durchdachtem Prozess.

Was das für Shopbetreiber bedeutet

Die Datenbankdiagnose ist keine Spezialdisziplin für den Ausnahmefall, sondern eine wiederkehrende Wartungsaufgabe wie das Einspielen von Aktualisierungen. Ein Shop, dessen Katalog um dreißig Prozent wächst, hat danach andere Abfrageprofile als vorher, und ein Index, der vor zwei Jahren gepasst hat, kann heute überflüssig sein. Der Aufwand für einen vollständigen Durchlauf liegt erfahrungsgemäß bei zwei bis drei Personentagen, verteilt über einige Wochen Laufzeit - deutlich weniger als eine Serveraufrüstung, die dasselbe Symptom nur verschiebt. In der Beratung und Konzeption ordnen wir diesen Aufwand gegen die Alternativen ein.

Wenn Sie ein größeres Projekt planen, gehört das Thema früh in die Anforderungen: Wer im Vorfeld festhält, welche Abfragen unter welcher Last wie schnell antworten sollen, spart sich die Nachverhandlung im Betrieb. Formulierungshilfen dafür stehen im Beitrag zum Lastenheft für ein Shop-Projekt. Und wer nur wissen will, ob sich der Aufwand überhaupt lohnt, schaltet das Protokoll für eine Woche ein und sieht sich die Summenspalte an - diese Antwort kostet eine Konfigurationszeile.

Quellen und Studien

Dieser Artikel basiert auf Daten von MariaDB, MySQL, web.dev, dem HTTP Archive Web Almanac 2025 und Bitkom. Die genannten Zahlen beziehen sich auf den Stand der jeweiligen Veröffentlichung.

Ja. slow_query_log, long_query_time und min_examined_row_limit lassen sich zur Laufzeit setzen, ein Neustart ist dafür nicht nötig. Der Eintrag in der Konfigurationsdatei sorgt dafür, dass die Einstellung einen Neustart überdauert. Planen Sie den Platz auf der Partition ein: Bei einer Schwelle von 0,2 Sekunden und aktiviertem Indexfilter kann das Protokoll auf einem gut besuchten Shop mehrere Gigabyte pro Tag erreichen.

Der voreingestellte Wert liegt bei zehn Sekunden (MariaDB) und ist für einen Shop zu hoch. Beginnen Sie bei 0,2 Sekunden und senken Sie den Wert ab, sobald die auffälligen Einträge abgearbeitet sind. Der kleinste zulässige Wert ist 0, und die Angabe ist bis in den Mikrosekundenbereich möglich (MySQL) - eine Schwelle von 0 protokolliert allerdings jede Abfrage und ist nur für kurze, eng begrenzte Messläufe sinnvoll.

EXPLAIN zeigt den geplanten Weg mit geschätzten Zeilenzahlen, ohne die Abfrage auszuführen. ANALYZE ruft den Optimierer auf, führt die Anweisung aus und erzeugt dann eine EXPLAIN-Ausgabe anstelle der Ergebnismenge (MariaDB). Dadurch stehen neben der Schätzung die gemessenen Werte r_rows und r_filtered. Auf Produktivsystemen ist zu bedenken, dass die Abfrage bei der messenden Variante tatsächlich läuft.

Der häufigste Grund ist ein Literal im falschen Typ. Bei Vergleichen einer Zeichenkettenspalte mit einer Zahl kann der Server keinen Index auf der Spalte verwenden, um den Wert schnell nachzuschlagen (MySQL). Der zweite häufige Grund ist eine Funktion um die Spalte, etwa DATE(spalte) = .... Der dritte ist die Präfixregel: Ein zusammengesetzter Index greift nur, wenn die erste Spalte im Filter vorkommt.

Ein Index kann aus bis zu 16 Spalten bestehen (MySQL). In der Praxis reichen zwei bis vier, weil der Server mehrspaltige Indizes für Abfragen nutzt, die alle Spalten prüfen, oder für Abfragen, die nur die erste Spalte, die ersten beiden Spalten und so weiter prüfen (MySQL). Jede weitere Spalte vergrößert den Index und verlangsamt Schreibvorgänge, ohne die Zahl der bedienbaren Abfragen im gleichen Maß zu erhöhen.

Ja. Wir richten das Protokoll ein, werten den Messlauf aus, priorisieren nach Gesamtzeit statt nach Spitzenwert und setzen die Änderungen einzeln um, damit jede für sich messbar bleibt. Dazu gehört die Gegenprobe auf der Schreibseite, damit ein neuer Index den Importlauf nicht ausbremst. Sprechen Sie uns über das Kontaktformular an oder sehen Sie sich unsere Leistungen im E-Commerce an.