Datenbank Monitoring: langsame Queries in MySQL und PostgreSQL finden und beheben
Welche Metriken zählen, welche Bordmittel MySQL und PostgreSQL mitbringen, wie Sie langsame Queries mit Traces verbinden und eine Triage-Checkliste zum Kopieren.
Zusammenfassung
- Gutes Datenbank Monitoring verbindet wenige Gesundheitsmetriken wie Latenz-Perzentile, Verbindungssättigung, Locks und Replikationsverzögerung mit Statistiken pro Query aus der Datenbank selbst.
- MySQL und PostgreSQL bringen die nötigen Werkzeuge mit: Slow Query Log und performance_schema in MySQL, pg_stat_statements, auto_explain und EXPLAIN (ANALYZE, BUFFERS) in PostgreSQL.
- Die meisten langsamen Queries entstehen in der Anwendung, deshalb macht erst die Korrelation mit Distributed Traces aus einer Liste langsamer Statements eine konkrete Lösung.
Datenbank Monitoring bedeutet, das Verhalten Ihrer Datenbanken unter realer Last zu messen und die Queries, Sperren und Ressourcengrenzen zu finden, die Nutzer warten lassen. Wird eine Checkout-Seite oder ein API-Endpunkt langsam, gerät die Datenbank schnell unter Verdacht. Die eigentliche Ursache liegt aber oft woanders: in einem Muster im Anwendungscode, einem fehlenden Index oder einem erschöpften Connection Pool.
Dieser Leitfaden ist praxisnah und herstellerneutral. Er zeigt, welche Metriken sich lohnen, mit welchen Bordmitteln Sie in MySQL und PostgreSQL langsame Queries finden, wie Sie ein langsames Statement dem auslösenden Request zuordnen, wie Connection Pooling mit PgBouncer und ProxySQL funktioniert, welche Alerts sinnvoll sind, und er endet mit einer Triage-Checkliste für Ihr Runbook.
Die wichtigsten Metriken im Datenbank Monitoring
Eine kurze Liste von Metriken deckt die meisten Störungen ab. Erfassen Sie sie pro Instanz und, wo möglich, pro Datenbank oder Query-Digest:
- Latenz-Perzentile: p50, p95 und p99 der Ausführungsdauer. Durchschnittswerte verdecken die langsamen Ausreißer; ein Perzentil zeigt, was die langsamsten Anfragen erleben. Beobachten Sie die Latenz Ihrer häufigsten Queries einzeln und nicht nur als einen Wert für die ganze Instanz.
- Durchsatz: Queries oder Transaktionen pro Sekunde, getrennt nach Lesen und Schreiben. Steigt die Latenz bei gleichem Durchsatz, deutet das auf Konkurrenz um Ressourcen hin; steigen beide, eher auf zusätzliche Last.
- Verbindungen und Pool-Auslastung: aktive Verbindungen im Verhältnis zu
max_connectionssowie Clients, die auf eine Verbindung aus dem Pool warten. Wartezeit im Pool taucht in Datenbankstatistiken nicht auf, Nutzer spüren sie aber sofort. Verbindungslimits sind ein klassisches Signal für Sättigung. - Locks und Deadlocks: Lock-Wartezeit, Zahl blockierter Sessions, Deadlocks und das Alter der ältesten offenen Transaktion.
- Replikationsverzögerung: wie weit Replikate hinter dem Primary liegen. Verzögerung führt zu veralteten Lesezugriffen und macht Failover riskant.
- Buffer- und Cache-Trefferquote: Anteil der Seitenzugriffe aus dem Speicher. Sinkt die Quote, passt der Working Set nicht mehr in den Speicher oder eine Query liest deutlich mehr Daten als zuvor.
- Disk-I/O: Lese- und Schreib-IOPS, Durchsatz, I/O-Latenz und Queue-Tiefe sowie temporäre Dateien aus Sortierungen und Hash-Operationen.
Eine hohe Cache-Trefferquote beweist keine gesunde Datenbank. Eine Query, die zehn Millionen Zeilen aus dem Cache liest, um zehn zurückzugeben, bleibt langsam. Nutzen Sie die Quote als Trend und bewerten Sie Effizienz über gelesene Zeilen oder Buffer pro Query.
Langsame Queries in MySQL finden
Das Slow Query Log
Das Slow Query Log protokolliert Statements, die länger als long_query_time Sekunden laufen. Es ist standardmäßig deaktiviert, und der Standardwert von 10 Sekunden ist für die meisten Webanwendungen viel zu hoch. Der Wert akzeptiert Nachkommastellen bis zur Mikrosekunde, üblich sind Schwellen wie 0,5 oder 0,2 Sekunden:
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 0.5 # Sekunden, Beispielwert
log_queries_not_using_indexes = 1 # optional, kann viel loggen
log_throttle_queries_not_using_indexes = 60
min_examined_row_limit = 1000 # Beispielwert
log_slow_extra = 1 # zusätzliche Felder im FILE-Output
Über log_output legen Sie fest, ob Einträge in eine Datei, in die Tabelle mysql.slow_log oder in beide geschrieben werden. Werkzeuge wie mysqldumpslow oder pt-query-digest von Percona fassen das Log nach normalisierten Statements zusammen, was deutlich aussagekräftiger ist als einzelne Einträge.
performance_schema und sys-Schema
Das Slow Query Log sieht nur Statements oberhalb der Schwelle. Das performance_schema, in aktuellen MySQL-Versionen standardmäßig aktiv, aggregiert jedes Statement nach Digest. Die Timer-Spalten sind in Pikosekunden angegeben, ab MySQL 8.0 gibt es zusätzlich Perzentil-Spalten:
SELECT digest_text,
count_star AS aufrufe,
ROUND(sum_timer_wait / 1e12, 1) AS gesamt_s,
ROUND(avg_timer_wait / 1e9, 2) AS avg_ms,
ROUND(quantile_95 / 1e9, 2) AS p95_ms,
sum_rows_examined,
sum_no_index_used
FROM performance_schema.events_statements_summary_by_digest
ORDER BY sum_timer_wait DESC
LIMIT 10;
Sortieren Sie zuerst nach Gesamtzeit: Eine 5-ms-Query, die zwei Millionen Mal pro Stunde läuft, kostet oft mehr als ein Report mit drei Sekunden. Das sys-Schema bietet fertige Views wie sys.statement_analysis und sys.statements_with_full_table_scans. Für Sperren zeigt SHOW ENGINE INNODB STATUS den zuletzt erkannten Deadlock, mit innodb_print_all_deadlocks landet jeder Deadlock im Error Log. Die Replikationsverzögerung finden Sie als Seconds_Behind_Source in SHOW REPLICA STATUS.
Langsame Queries in PostgreSQL finden
pg_stat_statements
Die Extension pg_stat_statements ist das wichtigste Einzelwerkzeug für PostgreSQL Monitoring. Sie muss beim Serverstart geladen und anschließend in der Datenbank angelegt werden:
# postgresql.conf (Neustart erforderlich)
shared_preload_libraries = 'pg_stat_statements'
track_io_timing = on
-- danach in der Datenbank
CREATE EXTENSION pg_stat_statements;
SELECT query, calls,
ROUND(total_exec_time::numeric, 0) AS gesamt_ms,
ROUND(mean_exec_time::numeric, 2) AS mittel_ms,
rows, shared_blks_hit, shared_blks_read
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
Seit PostgreSQL 13 heißen die Spalten total_exec_time und mean_exec_time; ältere Anleitungen verwenden noch total_time. Ist shared_blks_read im Verhältnis zu shared_blks_hit hoch, liest die Query aus dem Dateisystem statt aus den Shared Buffers.
Logging und auto_explain
log_min_duration_statement protokolliert jedes Statement, das länger als der angegebene Wert in Millisekunden läuft (-1 deaktiviert, 0 loggt alles). Das Modul auto_explain geht weiter und schreibt den Ausführungsplan langsamer Statements ins Log. Das hilft besonders bei Queries, die nur mit bestimmten Parametern langsam sind:
log_min_duration_statement = 500 # ms, Beispielwert
log_lock_waits = on # loggt Wartezeiten über deadlock_timeout
session_preload_libraries = 'auto_explain'
auto_explain.log_min_duration = 1000 # ms, Beispielwert
auto_explain.log_analyze = on # verursacht Timing-Overhead
auto_explain.log_buffers = on
auto_explain.sample_rate = 0.1 # 10 % der betroffenen Statements
auto_explain.log_analyze misst jeden Plan-Knoten und kann spürbaren Overhead erzeugen. Arbeiten Sie deshalb mit Sampling oder aktivieren Sie es nur zeitweise. Bei Sperren zeigen pg_stat_activity mit wait_event_type und die Funktion pg_blocking_pids(), wer wen blockiert. pg_stat_database zählt Deadlocks, temporäre Dateien und Cache-Treffer, pg_stat_replication liefert write_lag, flush_lag und replay_lag.
EXPLAIN (ANALYZE, BUFFERS)
Haben Sie einen Verdächtigen, führen Sie ihn mit echten Parametern in einer sicheren Umgebung oder auf einem Replikat aus:
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders
WHERE customer_id = 4711 AND status = 'open'
ORDER BY created_at DESC LIMIT 20;
Vergleichen Sie geschätzte und tatsächliche Zeilenzahlen und achten Sie auf Sequential Scans über große Tabellen, Sortierungen auf Disk und Nested Loops mit vielen Schleifen. ANALYZE führt das Statement wirklich aus; schreibende Statements gehören daher in eine Transaktion, die Sie zurückrollen. Seit PostgreSQL 18 sind Buffer-Informationen bei ANALYZE automatisch enthalten. In MySQL liefert EXPLAIN ANALYZE (ab 8.0.18) eine vergleichbare Sicht.
Symptom, wahrscheinliche Ursache, wo nachsehen
Nutzen Sie die Tabelle als erste Orientierung, wenn ein Datenbank-Alert auslöst:
| Symptom | Wahrscheinliche Ursache | Wo nachsehen |
|---|---|---|
| p99-Latenz steigt, Durchsatz konstant | Lock-Konkurrenz, Planwechsel, kalter Cache | Lock-Wartezeiten, Waits in pg_stat_activity / performance_schema, EXPLAIN der Top-Digests |
| Viele gleiche schnelle Queries pro Request | N+1-Muster im ORM | Spans pro Request im Trace, calls pro Digest |
| Einzelne Query langsam, viele gelesene Zeilen | Fehlender oder unbrauchbarer Index | EXPLAIN, sum_no_index_used, Sequential Scans in pg_stat_user_tables |
| Timeouts in der App, Datenbank wirkt ruhig | Pool erschöpft oder Connection Leak | Pool-Wartezeit, PgBouncer SHOW POOLS, Sessions im Status idle in transaction |
Verbindungen nahe max_connections | Kein Pooling, zu viele App-Instanzen | Threads_connected, pg_stat_activity nach Client |
| Deadlock-Fehler | Transaktionen sperren Zeilen in unterschiedlicher Reihenfolge | SHOW ENGINE INNODB STATUS, PostgreSQL-Serverlog |
| Veraltete Daten auf Replikaten | Replikationsverzögerung durch Schreiblast oder lange Queries | Seconds_Behind_Source, pg_stat_replication |
| Trefferquote sinkt, Disk-Reads steigen | Working Set größer als Speicher, neuer Scan | Buffer-Pool-Metriken, shared_blks_read, I/O-Latenz |
| Temporäre Dateien, Sortierungen auf Disk | Große Sortierungen oder Hashes, wenig Arbeitsspeicher | temp_files in pg_stat_database, Created_tmp_disk_tables |
Langsame Queries mit Application Traces verbinden
Datenbankstatistiken zeigen, welche Statements teuer sind. Sie zeigen nicht, welcher Endpunkt, welche Nutzeraktion oder welches Deployment sie verursacht hat. Genau dafür gibt es Distributed Tracing: Die OpenTelemetry-Instrumentierung von Datenbank-Clients erzeugt pro Query einen Span mit Attributen wie Datenbanksystem und bereinigtem Query-Text, eingebettet in den HTTP-Request, der ihn ausgelöst hat.
Liegen Traces neben den Datenbankmetriken, werden drei häufige Probleme sichtbar:
- N+1-Abfragen: Ein Request lädt eine Liste und danach jedes Element mit einer eigenen Query. Jedes Statement ist schnell und erscheint nie im Slow Query Log, der Trace zeigt aber Hunderte fast identischer Spans. Unser Lexikoneintrag zur N+1-Abfrage erklärt das Muster und typische Lösungen wie Eager Loading oder Batching.
- Fehlende Indizes nach einem Release: Ein neuer Filter oder eine neue Sortierung im Code erzeugt einen Full Table Scan. Der Vergleich der Traces vor und nach dem Deployment zeigt den neuen Span, EXPLAIN bestätigt den fehlenden Index.
- Nebenwirkungen des ORM: Lazy Loading, Abfragen aller Spalten, implizite Transaktionen, die über Remote-Aufrufe hinweg offen bleiben, oder Retries, die die Last vervielfachen. Im Trace erscheinen sie als lange Lücken oder wiederholte Spans.
Um vom Logeintrag der Datenbank direkt zum Trace zu springen, hängen manche Teams den Trace-Kontext als SQL-Kommentar an, etwa nach der SQLCommenter-Konvention. Prüfen Sie, wie Ihre Datenbank-Tools mit Kommentaren umgehen, denn bei der Normalisierung können sie entfernt oder beibehalten werden.
Connection Pooling mit PgBouncer und ProxySQL
Jede PostgreSQL-Verbindung ist ein eigener Serverprozess, jede MySQL-Verbindung belegt einen Thread und Speicher. Dutzende Anwendungs-Pods mit eigenen Pools können den Server erschöpfen, lange bevor die CPU zum Engpass wird. Ein Pooler zwischen Anwendung und Datenbank löst das:
- PgBouncer für PostgreSQL. Mit
pool_mode = transactionwird eine Serververbindung nur für die Dauer einer Transaktion vergeben, sodass sich viele Clients wenige Verbindungen teilen. Session-Zustand wieSEToder Advisory Locks auf Session-Ebene bleibt zwischen Transaktionen nicht erhalten; prüfen Sie daher die Einstellungen von Treiber und ORM.SHOW POOLSin der Admin-Konsole liefertcl_waitingundmaxwait, die direktesten Indikatoren für einen gesättigten Pool. - ProxySQL für MySQL. Es bündelt Verbindungen per Multiplexing, kann Lesezugriffe über Query Rules auf Replikate lenken und speichert Statistiken pro Digest in
stats_mysql_query_digest.
Überwachen Sie den Pooler wie einen eigenen Service: wartende Clients, Wartezeit, belegte Serververbindungen und Fehler. Beobachten Sie zusätzlich den Pool in der Anwendung, denn dort steht ein Request meist zuerst in der Warteschlange.
Sinnvolle Alerts für Datenbanken
Alarmieren Sie auf Symptome, die Nutzer spüren, und auf Sättigung, die einen Ausfall ankündigt. Die folgenden Schwellen sind Beispielwerte als Ausgangspunkt, keine allgemeingültige Empfehlung; justieren Sie sie an Ihrer eigenen Baseline:
- p95- oder p99-Latenz kritischer Query-Digests oder Datenbank-Spans 10 Minuten lang über der Baseline.
- Verbindungen über 80 % von
max_connectionsoder Clients, die länger als eine Minute auf den Pool warten. - Replikationsverzögerung über dem, was Ihre Read-after-Write-Logik verträgt, zum Beispiel 30 Sekunden.
- Transaktionen oder Sessions im Status idle in transaction, die älter als beispielsweise 15 Minuten sind.
- Deadlock-Rate deutlich über dem Normalniveau statt eines einzelnen Deadlocks.
- Prognose, dass der Speicherplatz in wenigen Tagen ausgeht, sowie anhaltend erhöhte I/O-Latenz.
Koppeln Sie Datenbank-Alerts wo möglich an Service Level Objectives statt an starre Schwellen. Wie das geht, zeigt unser Beitrag zu SLOs in der Praxis, und der Leitfaden zum Reduzieren von Alert Fatigue hilft, die Liste kurz zu halten.
Triage-Checkliste für langsame Queries
Kopieren Sie diese Checkliste in Ihr Runbook und arbeiten Sie sie der Reihe nach ab:
TRIAGE-CHECKLISTE LANGSAME QUERIES
[ ] 1. Eingrenzen: welcher Service, Endpunkt, Zeitraum? Seit einem Deployment?
[ ] 2. Auswirkung: p95/p99-Latenz, Fehlerrate, Timeouts in der App
[ ] 3. Sättigung: Verbindungen vs. max_connections, Pool-Wartezeit,
CPU, I/O-Latenz, Speicherplatz
[ ] 4. Top-Digests nach Gesamtzeit (pg_stat_statements /
events_statements_summary_by_digest): neu oder verändert?
[ ] 5. Aufrufe pro Request im Trace: N+1-Muster oder Retries?
[ ] 6. Sperren: blockierte Sessions, älteste Transaktion, Deadlocks
[ ] 7. Plan: EXPLAIN (ANALYZE, BUFFERS) / EXPLAIN ANALYZE mit echten
Parametern; geschätzte vs. tatsächliche Zeilen
[ ] 8. Index: fehlt er, wird er nicht genutzt, falsche Spaltenreihenfolge?
Veraltete Statistiken?
[ ] 9. Replikationsverzögerung, falls Lesezugriffe auf Replikate gehen
[ ] 10. Lösung: Index, Query umschreiben, Batching, Pool-Größe, Timeout
[ ] 11. Prüfen: Latenz und Digest-Statistik vorher/nachher vergleichen
[ ] 12. Vorbeugen: Alert, Regressionstest oder Query-Review ergänzen
Wie Obseria unterstützt
Obseria erfasst Datenbankmetriken, Logs und Traces über OpenTelemetry und speichert sie gemeinsam in Obseria Lake. Eine langsame Query, der auslösende Request und das Deployment, das sie verändert hat, liegen dadurch an einem Ort. Die Funktion Datenbank-Monitoring und die Integrationen für PostgreSQL und MySQL decken die oben beschriebenen Metriken ab.
- Query-Statistiken verknüpft mit Trace-Spans, sodass N+1-Muster und Kosten pro Endpunkt sichtbar werden.
- Obseria Pipeline maskiert Literalwerte und personenbezogene Daten im Query-Text vor der Speicherung.
- Obseria Intelligence korreliert Latenzänderungen mit Deployments, Sperren und Sättigung und legt die Belege offen.
- SLOs mit Burn-Rate-Alerts für datenbankgestützte Services.
Probieren Sie das mit Ihren eigenen Datenbanken in der kostenlosen Testversion aus.