vom Zufallsfund zum wiederholbaren Prozess
Wer langsame Queries erst findet, wenn Nutzer sich beschweren, arbeitet reaktiv statt praeventiv. Mit Slow Query Log, pg_stat_statements und einem klaren Sampling-Prozess lassen sich langsame Queries systematisch aufspueren, bevor sie zum Produktionsproblem werden, unabhaengig davon, welches Datenbanksystem im Einsatz ist.
Inhaltsverzeichnis
- 1. Warum langsame Queries selten von allein auffallen
- 2. Slow Query Log als erste Diagnosequelle
- 3. pg_stat_statements: Sampling statt Logging
- 4. Schwellwerte richtig setzen
- 5. Aggregation: die zehn teuersten Query-Muster finden
- 6. APM und Datenbank-Metriken korrelieren
- 7. Sampling in Produktion ohne Overhead
- 8. Ein wiederholbarer Diagnoseprozess
- 9. Werkzeuge im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum langsame Queries selten von allein auffallen
Eine langsame Query faellt in den seltensten Faellen sofort auf. Sie beginnt meist unauffaellig: Eine Tabelle waechst langsam, ein Index fehlt fuer einen selten genutzten Filter, oder eine Anwendung generiert unter bestimmten Bedingungen eine Abfrage, die niemand beim Entwurf getestet hat. Solange die Datenmenge klein ist, laeuft die Query in wenigen Millisekunden. Erst wenn die Tabelle auf hunderttausende oder Millionen Zeilen waechst, wird aus einer harmlosen Abfrage eine langsame Query, die den gesamten Request blockiert.
Das Tueckische daran: Ohne systematisches Monitoring bemerkt niemand den schleichenden Uebergang. Die durchschnittliche Antwortzeit einer Anwendung kann ueber Wochen unauffaellig bleiben, waehrend im Hintergrund einzelne Abfragen bereits im Sekundenbereich laufen, aber selten genug aufgerufen werden, um den Mittelwert zu verzerren. Genau hier entsteht der Bedarf, langsame Queries systematisch zu finden, statt auf Nutzerbeschwerden oder zufaellige Beobachtungen im Log zu warten.
Ein systematischer Ansatz kombiniert drei Bausteine: eine kontinuierliche Erfassungsquelle direkt in der Datenbank, klar definierte Schwellwerte fuer das, was als langsam gilt, und einen wiederholbaren Prozess, der aus rohen Logdaten priorisierte Handlungsempfehlungen macht. Die folgenden Abschnitte zeigen, wie dieser Prozess fuer MySQL und PostgreSQL konkret aussieht und welche Werkzeuge dabei zum Einsatz kommen.
2. Slow Query Log als erste Diagnosequelle
Das Slow Query Log ist in MySQL und MariaDB die naheliegendste Quelle, um langsame Queries zu finden. Es protokolliert jede Abfrage, deren Ausfuehrungszeit einen konfigurierten Schwellwert ueberschreitet, zusammen mit Zeitstempel, gepruefter Zeilenanzahl und der vollstaendigen Query. Aktiviert wird es ueber die Systemvariable slow_query_log, der Schwellwert ueber long_query_time. Wichtig: Das Slow Query Log schreibt standardmaessig in eine Datei, kann aber auch direkt in eine Tabelle protokollieren, was die Auswertung mit SQL selbst vereinfacht.
PostgreSQL kennt kein natives Slow Query Log im gleichen Sinn, bietet aber mit log_min_duration_statement eine funktional aequivalente Einstellung: Jede Anweisung, die laenger als der konfigurierte Wert in Millisekunden dauert, landet im Server-Log. Der entscheidende Unterschied zu MySQL: PostgreSQL loggt in die normale Logdatei, nicht in eine separate Tabelle, weshalb Tools wie pgBadger zur Auswertung fast obligatorisch sind, wenn man langsame Queries systematisch finden will, ohne Logdateien von Hand zu durchsuchen.
-- MySQL / MariaDB: Slow Query Log aktivieren und in Tabelle schreiben
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1.0; -- Schwellwert in Sekunden
SET GLOBAL log_output = 'TABLE'; -- statt FILE: landet in mysql.slow_log
SET GLOBAL log_queries_not_using_indexes = 'ON';
-- Auswertung direkt per SQL, keine externe Toolchain noetig
SELECT
sql_text,
query_time,
lock_time,
rows_examined,
rows_sent,
start_time
FROM mysql.slow_log
ORDER BY query_time DESC
LIMIT 20;
Fuer PostgreSQL sieht die aequivalente Konfiguration in der postgresql.conf so aus, dass eine langsame Query ab einer bestimmten Dauer geloggt wird, ohne dass jede Anweisung im System protokolliert werden muss, was den Log-Overhead im Rahmen haelt:
-- postgresql.conf: langsame Queries ab 500ms loggen
log_min_duration_statement = 500
log_line_prefix = '%m [%p] user=%u,db=%d '
log_checkpoints = on
log_lock_waits = on
-- Auswertung ueber pgBadger (Kommandozeile, nicht SQL):
-- pgbadger /var/log/postgresql/postgresql-*.log -o report.html
3. pg_stat_statements: Sampling statt Logging
Die Extension pg_stat_statements loest ein Problem, das reines Logging nicht loesen kann: Sie aggregiert Ausfuehrungsstatistiken pro normalisiertem Query-Muster, statt jede einzelne Ausfuehrung zu protokollieren. Zwei Abfragen, die sich nur im konkreten Wert einer WHERE-Bedingung unterscheiden, werden zu einem einzigen Eintrag zusammengefasst, inklusive Gesamtzeit, Aufrufanzahl und Durchschnittszeit. Das macht pg_stat_statements zum bevorzugten Werkzeug, um langsame Queries systematisch zu finden, ohne Gigabytes an Logdateien durchsuchen zu muessen.
Der entscheidende Vorteil gegenueber dem reinen Logging: pg_stat_statements erfasst auch Queries, die einzeln betrachtet schnell sind, aber durch massenhafte Wiederholung in Summe erhebliche Last erzeugen, ein Muster, das beim reinen Schwellwert-Logging komplett unsichtbar bleibt. Genau diese Faelle sind es oft, die eine Datenbank unter Last kippen lassen, obwohl kein einzelner Log-Eintrag als kritisch auffaellt.
-- Extension einmalig aktivieren (Superuser oder pg_monitor-Rolle)
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- Die zehn teuersten Query-Muster nach Gesamtzeit
SELECT
query,
calls,
total_exec_time,
mean_exec_time,
rows,
total_exec_time / NULLIF(calls, 0) AS avg_ms_per_call
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
-- Statistiken zuruecksetzen nach einem Deployment,
-- um Vorher-Nachher-Vergleiche sauber zu isolieren
SELECT pg_stat_statements_reset();
4. Schwellwerte richtig setzen
Ein zu niedriger Schwellwert erzeugt Log-Flut und Overhead, ein zu hoher Schwellwert laesst relevante langsame Queries unentdeckt. Die Praxis zeigt: Ein guter Startwert orientiert sich am 95. oder 99. Perzentil der normalen Antwortzeiten, nicht an einem willkuerlichen Fixwert wie einer Sekunde. Eine API, die im Median 20 Millisekunden pro Datenbankaufruf braucht, hat bei 500 Millisekunden bereits ein ernstes Problem, waehrend ein Batch-Reporting-System mit typischerweise 2 Sekunden pro Abfrage einen ganz anderen Massstab braucht.
Ein zweistufiger Ansatz hat sich bewaehrt: Ein niedriger Schwellwert fuer die permanente Aggregation via pg_stat_statements oder die Tabellen-Variante des Slow Query Logs, und ein hoeherer Schwellwert fuer detailliertes Logging mit vollstaendigem Query-Text und Explain-Ausgabe. So bleibt der permanente Overhead gering, waehrend wirklich kritische langsame Queries mit vollem Kontext erfasst werden. Wichtig ist zudem, den Schwellwert regelmaessig neu zu bewerten, weil sich das Normalmass der Anwendung mit wachsenden Datenmengen verschiebt.
5. Aggregation: die zehn teuersten Query-Muster finden
Rohe Logeintraege sind fuer die Priorisierung wenig hilfreich, solange sie nicht aggregiert werden. Die entscheidende Frage lautet nicht "welche einzelne Query war am langsamsten", sondern "welches Query-Muster verursacht in Summe die meiste Last". Eine Query, die 5000 Mal pro Minute mit 10 Millisekunden laeuft, kostet in Summe mehr Datenbankzeit als eine Query, die einmal pro Stunde 2 Sekunden braucht. Wer langsame Queries systematisch finden will, muss deshalb nach total_exec_time sortieren, nicht nach mean_exec_time allein.
Fuer MySQL uebernimmt diese Aggregation traditionell pt-query-digest aus dem Percona Toolkit, das Slow-Log-Dateien einliest, Queries normalisiert (Literale durch Platzhalter ersetzt) und nach Gesamtzeit gruppiert. Das Ergebnis ist ein Report, der genau die Query-Muster oben zeigt, die den groessten Anteil der Gesamtzeit ausmachen, unabhaengig davon, ob eine einzelne Ausfuehrung als "langsam" auffiel.
-- pt-query-digest (Kommandozeile, Percona Toolkit):
-- pt-query-digest /var/log/mysql/slow.log > digest-report.txt
-- Aequivalente Aggregation direkt in SQL, wenn Slow Log in Tabelle liegt
SELECT
-- Normalisierung: Zahlenwerte durch Platzhalter ersetzen
REGEXP_REPLACE(sql_text, '[0-9]+', 'N') AS query_pattern,
COUNT(*) AS call_count,
SUM(query_time) AS total_seconds,
AVG(query_time) AS avg_seconds,
MAX(query_time) AS worst_case
FROM mysql.slow_log
WHERE start_time > NOW() - INTERVAL 1 DAY
GROUP BY query_pattern
ORDER BY total_seconds DESC
LIMIT 10;
6. APM und Datenbank-Metriken korrelieren
Datenbank-interne Erfassung zeigt, welche Query langsam ist, aber nicht immer, warum sie gerade jetzt langsam wurde. Application Performance Monitoring, etwa mit Tools wie New Relic, Datadog oder selbst gehosteten Loesungen auf Basis von OpenTelemetry, ordnet eine langsame Query einem konkreten HTTP-Request, einem Nutzer-Workflow oder einem Deployment-Zeitpunkt zu. Diese Korrelation ist entscheidend, um zwischen "diese Query ist grundsaetzlich schlecht geschrieben" und "diese Query wurde erst durch das gestrige Deployment langsam" zu unterscheiden.
Ein bewaehrtes Muster ist, Trace-IDs aus dem Anwendungscode als SQL-Kommentar an jede Query anzuhaengen. So laesst sich ein Eintrag im Slow Query Log direkt einem Trace im APM-System zuordnen, ohne Zeitstempel manuell abgleichen zu muessen. Dieses Muster ist framework-unabhaengig und funktioniert mit jedem SQL-Client, der rohe Query-Strings erlaubt.
-- Trace-Kontext als SQL-Kommentar fuer spaetere Korrelation
/* trace_id=7f3a9c21 route=/checkout/submit user_id=48213 */
SELECT o.id, o.total, o.status
FROM orders o
WHERE o.customer_id = 48213
AND o.status = 'pending'
ORDER BY o.created_at DESC
LIMIT 5;
-- Im Slow Log erscheint der Kommentar mit,
-- Filterung auf einen Trace wird damit trivial:
-- WHERE sql_text LIKE '%trace_id=7f3a9c21%'
7. Sampling in Produktion ohne spuerbaren Overhead
Vollstaendiges Logging jeder Query in einem Hochlast-System erzeugt selbst spuerbaren Overhead und kann paradoxerweise genau die Latenz verursachen, die es aufdecken soll. pg_stat_statements loest dieses Problem elegant, weil es nur aggregierte Zaehler pro Query-Muster fuehrt, nicht jede Ausfuehrung einzeln schreibt. Der Overhead liegt typischerweise im niedrigen einstelligen Prozentbereich und ist damit auch in Produktionssystemen mit hohem Durchsatz vertretbar.
Fuer MySQL empfiehlt sich in Hochlast-Umgebungen ein probabilistisches Sampling: Statt jede Query zu loggen, wird nur ein Bruchteil, etwa jede hundertste Ausfuehrung, protokolliert, dafuer aber mit vollem Kontext inklusive Explain-Plan. Das reduziert den Log-Overhead drastisch, waehrend statistisch relevante Muster weiterhin sichtbar bleiben. Wichtig dabei: Das Sampling darf nicht rein zeitbasiert erfolgen, weil dann seltene, aber besonders teure Queries systematisch uebersehen werden koennten.
8. Ein wiederholbarer Diagnoseprozess
Ohne festen Prozess bleibt das Aufspueren langsamer Queries Zufall, abhaengig davon, wer gerade zufaellig ins Log schaut. Ein wiederholbarer Prozess sieht so aus: Erstens, taegliche oder woechentliche automatisierte Auswertung der Top-Query-Muster nach Gesamtzeit. Zweitens, ein definierter Schwellwert, ab dem ein Muster als Ticket in die Priorisierung wandert. Drittens, ein Vorher-Nachher-Vergleich nach jeder Optimierung, um zu verifizieren, dass die Aenderung tatsaechlich gewirkt hat und keine neue langsame Query an anderer Stelle entstanden ist.
Ein haeufiger Fehler ist, diesen Prozess nur reaktiv nach Incidents zu betreiben, statt ihn als festen Bestandteil des Deployment-Zyklus zu etablieren. Teams, die pg_stat_statements_reset() vor jedem grossen Release ausfuehren und danach die Top-Ten-Liste mit der Vorwoche vergleichen, erkennen Regressionen innerhalb von Stunden statt Wochen. Genau dieser proaktive Rhythmus ist der Unterschied zwischen zufaelligem Finden und systematischem Finden langsamer Queries.
9. Werkzeuge im Vergleich
Die Wahl des richtigen Werkzeugs haengt stark vom Datenbanksystem und der Belastungssituation ab. Die folgende Tabelle vergleicht die wichtigsten Ansaetze, um langsame Queries systematisch zu finden, nach Overhead, Detailgrad und Einsatzbereich.
| Werkzeug | Datenbank | Overhead | Staerke |
|---|---|---|---|
| Slow Query Log | MySQL, MariaDB | gering bis mittel | vollstaendiger Query-Text pro Ausfuehrung |
| pg_stat_statements | PostgreSQL | sehr gering | aggregierte Statistik pro Query-Muster |
| pt-query-digest | MySQL, MariaDB | offline, kein Live-Overhead | Normalisierung und Priorisierung von Logdateien |
| pgBadger | PostgreSQL | offline, kein Live-Overhead | HTML-Reports aus PostgreSQL-Logdateien |
| APM (OpenTelemetry) | datenbankuebergreifend | gering | Korrelation mit Request und Deployment |
In der Praxis kombinieren erfahrene Teams mehrere dieser Werkzeuge: pg_stat_statements oder die Tabellen-Variante des Slow Query Logs fuer die kontinuierliche Basis-Erfassung, ergaenzt um APM-Korrelation fuer die Ursachenanalyse bei akuten Vorfaellen. Kein einzelnes Werkzeug deckt beide Anforderungen gleichzeitig ab, deshalb ist die Kombination der eigentliche Schluessel, um langsame Queries systematisch zu finden.
Mironsoft
Datenbank-Performance-Analyse und SQL-Debugging fuer produktive Systeme
Langsame Queries kosten Umsatz, nicht nur Millisekunden
Wir richten Slow Query Log, pg_stat_statements und einen wiederholbaren Diagnoseprozess in eurer Umgebung ein, damit langsame Queries auffallen, bevor Kunden sie melden.
Monitoring-Setup
Slow Query Log, pg_stat_statements und Alerting-Schwellwerte einrichten
Query-Audit
Bestehende Top-Ten-Queries analysieren und priorisierte Fixliste erstellen
Prozess-Etablierung
Wiederholbaren Diagnoseprozess in den Deployment-Zyklus integrieren
10. Zusammenfassung
Langsame Queries systematisch zu finden bedeutet, sich nicht auf Zufallsfunde oder Nutzerbeschwerden zu verlassen, sondern eine kontinuierliche Erfassungsquelle direkt in der Datenbank zu etablieren. Slow Query Log und pg_stat_statements liefern die Rohdaten, sinnvoll gesetzte Schwellwerte trennen relevante von irrelevanten Eintraegen, und Aggregation nach Gesamtzeit statt Einzelfall zeigt die tatsaechlich teuersten Query-Muster. Diese Kombination verwandelt das Aufspueren langsamer Queries von einer reaktiven Notmassnahme in einen planbaren, wiederholbaren Bestandteil des Betriebs.
Der groesste Hebel liegt darin, diesen Prozess in den Deployment-Zyklus zu integrieren, statt ihn nur bei akuten Vorfaellen zu aktivieren. Ein Team, das vor jedem Release die Statistiken zuruecksetzt und danach die Top-Ten-Liste vergleicht, erkennt Regressionen fruehzeitig und spart sich teure Incident-Nachtschichten. Die Werkzeuge dafuer sind in MySQL und PostgreSQL bereits eingebaut, es fehlt oft nur der feste Prozess, sie konsequent zu nutzen.
Langsame Queries systematisch finden — Das Wichtigste auf einen Blick
Erfassungsquelle
Slow Query Log (MySQL) oder pg_stat_statements (PostgreSQL) als kontinuierliche Basis, nicht als Einmalmassnahme.
Schwellwert
Am 95. bis 99. Perzentil der normalen Antwortzeit orientieren, nicht an einem willkuerlichen Fixwert.
Aggregation
Nach Gesamtzeit (total_exec_time) sortieren, nicht nach Durchschnitt allein, um die teuersten Muster zu finden.
Prozess
Statistiken vor jedem Release zuruecksetzen, danach Top-Ten-Vergleich als fester Bestandteil des Deployment-Zyklus.