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.

Obseria RedaktionObseria
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_connections sowie 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:

SymptomWahrscheinliche UrsacheWo nachsehen
p99-Latenz steigt, Durchsatz konstantLock-Konkurrenz, Planwechsel, kalter CacheLock-Wartezeiten, Waits in pg_stat_activity / performance_schema, EXPLAIN der Top-Digests
Viele gleiche schnelle Queries pro RequestN+1-Muster im ORMSpans pro Request im Trace, calls pro Digest
Einzelne Query langsam, viele gelesene ZeilenFehlender oder unbrauchbarer IndexEXPLAIN, sum_no_index_used, Sequential Scans in pg_stat_user_tables
Timeouts in der App, Datenbank wirkt ruhigPool erschöpft oder Connection LeakPool-Wartezeit, PgBouncer SHOW POOLS, Sessions im Status idle in transaction
Verbindungen nahe max_connectionsKein Pooling, zu viele App-InstanzenThreads_connected, pg_stat_activity nach Client
Deadlock-FehlerTransaktionen sperren Zeilen in unterschiedlicher ReihenfolgeSHOW ENGINE INNODB STATUS, PostgreSQL-Serverlog
Veraltete Daten auf ReplikatenReplikationsverzögerung durch Schreiblast oder lange QueriesSeconds_Behind_Source, pg_stat_replication
Trefferquote sinkt, Disk-Reads steigenWorking Set größer als Speicher, neuer ScanBuffer-Pool-Metriken, shared_blks_read, I/O-Latenz
Temporäre Dateien, Sortierungen auf DiskGroße Sortierungen oder Hashes, wenig Arbeitsspeichertemp_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 = transaction wird eine Serververbindung nur für die Dauer einer Transaktion vergeben, sodass sich viele Clients wenige Verbindungen teilen. Session-Zustand wie SET oder Advisory Locks auf Session-Ebene bleibt zwischen Transaktionen nicht erhalten; prüfen Sie daher die Einstellungen von Treiber und ORM. SHOW POOLS in der Admin-Konsole liefert cl_waiting und maxwait, 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_connections oder 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.

Teilen

Observability, die Antworten liefert.

Starten Sie kostenlos mit OpenTelemetry, ohne Kreditkarte.