Datenbankadministration für Sysadmins

Datenbankzustand beobachten: Momentaufnahme, Zähler und Wartevorgänge

In der Sitzungsübersicht steht ein DELETE auf eine große Fachtabelle. Daneben steht idle. Wer die Sitzung jetzt beendet, beendet eine, die nichts tut — denn die Spalte zeigt in diesem Zustand die zuletzt gelaufene Abfrage. Dieses Modul liest die Bordinstrumente beider Systeme so, wie ihre Dokumentation sie meint: Momentaufnahme gegen aufgelaufenen Zähler, Warten statt Auslastung, und die Felder, die stillschweigend leer bleiben, wenn das Überwachungskonto zu wenig darf.

Lehrtext · 10 Abschnitte · zuletzt geprüft: 2026-09-12

Ein Wert ist keine Diagnose

„Die Datenbank ist langsam“ und „die CPU steht bei 95 Prozent“ sind zwei Aussagen, und nur die zweite ist gemessen. Ob sie zusammengehören, ist eine eigene Frage — eine ausgelastete CPU kann der Grund für die Langsamkeit sein, ihre Folge, oder völlig unbeteiligt.

Datenbanken machen das besonders sichtbar, weil sie fast nie an einer einzelnen Ressource hängen. Eine Abfrage, die zehn Sekunden braucht, hat davon vielleicht neun gewartet: auf eine Sperre, auf eine Platte, auf das Protokoll. In dieser Zeit ist nichts ausgelastet, und trotzdem passiert nichts.

Die Umformulierung, die das Modul trägtNicht „was ist ausgelastet“, sondern „worauf wird gewartet“. Beide Systeme liefern genau dafür eigene Sichten — und beide haben Eigenheiten, die einen falschen Schluss näherlegen als den richtigen. Die folgenden Abschnitte behandeln vor allem diese Eigenheiten.

Zwei Sichten, die ständig verwechselt werden

Bevor irgendeine Zahl gelesen wird, gehört geklärt, welcher Art sie ist. Die Dokumentation trennt die beiden ausdrücklich und hält fest, dass die eine Einrichtung von der anderen unabhängig ist.

MomentaufnahmeWas gerade jetzt passiert: welche Sitzungen es gibt, was sie tun, worauf sie warten. In PostgreSQL pg_stat_activity, bei SQL Server sys.dm_exec_requests und sys.dm_exec_sessions.

Kumulativer ZählerWas sich seit einem Startpunkt aufsummiert hat: gelesene Blöcke, Wartezeiten, Vorgänge. In PostgreSQL die pg_stat_*-Sichten, bei SQL Server unter anderem sys.dm_os_wait_stats.

Die Unterscheidung ist nicht akademisch, weil die beiden verschiedene Fragen beantworten. Eine Momentaufnahme beantwortet „wer blockiert gerade wen“. Ein kumulativer Zähler beantwortet „worauf ist über einen Zeitraum am meisten Zeit gegangen“. Wer den Zähler für die erste Frage nimmt, bekommt einen Mittelwert über Wochen, in dem der aktuelle Vorfall untergeht.

Erfasst wird nur, was eingeschaltet istDie Sammlung kostet laut Dokumentation Rechenzeit bei jeder Abfrage, und deshalb lässt sich einstellen, was überhaupt erhoben wird. track_activities schaltet die Beobachtung des gerade ausgeführten Befehls ein, track_counts die kumulativen Zähler über Tabellen- und Indexzugriffe, track_io_timing die Messung der Zeiten für Lesen, Schreiben, Erweitern und Synchronisieren von Blöcken. Wer eine Sicht auswertet, deren Grundlage abgeschaltet ist, bekommt keine Fehlermeldung, sondern Nullen — und ohne track_io_timing fehlt genau die Größe, mit der sich Wartezeit auf Speicher überhaupt beziffern ließe.
Und sie sind unterschiedlich aktuellKumulative Statistiken werden laut Dokumentation nicht sofort sichtbar: Jeder Serverprozess schreibt seine gesammelten Werte erst kurz vor dem Leerlauf in den gemeinsamen Speicher, und nicht öfter als etwa einmal pro Sekunde. Eine noch laufende Abfrage oder Transaktion taucht in den Summen also gar nicht auf. Die Angaben zur aktuell laufenden Abfrage dagegen sind immer auf dem neuesten Stand.

Der Bezugszeitraum, den niemand nennt

Ein kumulativer Zähler ohne Angabe, seit wann er zählt, ist keine Kennzahl, sondern eine Zahl. Beide Systeme setzen diese Zähler unter Umständen zurück, ohne dass jemand etwas tut.

Bei SQL Server ist das ausdrücklich dokumentiert: Die Wartestatistiken werden über einen Neustart der Engine nicht hinweg aufbewahrt, und alle Daten sind kumulativ seit dem letzten Zurücksetzen oder dem Start der Engine. Zurücksetzen lässt sich die Sicht mit DBCC SQLPERF; den letzten Startzeitpunkt liefert die Spalte sqlserver_start_time in sys.dm_os_sys_info. Ohne diesen Bezug ist die größte Wartezahl bedeutungslos.

Bei PostgreSQL kommt ein zweiter Fall dazuWird der Server sauber heruntergefahren, legt er eine dauerhafte Kopie der Statistikdaten ab, sodass sie den Neustart überleben. Beim Start aus einem unsauberen Zustand dagegen werden laut Dokumentation alle Zähler zurückgesetzt — und die Dokumentation zählt dabei ausdrücklich auch den Start aus einer Grundsicherung und die Point-in-Time-Recovery auf. Nach genau der Maßnahme, die man nach einem Vorfall ergreift, ist die Vergleichsgrundlage also weg.

Praktisch heißt das: Jede Auswertung kumulativer Werte beginnt damit, den Startpunkt mit auszulesen. In PostgreSQL führen die Statistiksichten dafür eine eigene Spalte mit dem Zeitpunkt des letzten Zurücksetzens. Ohne diese Spalte im Bericht vergleicht man später Zeiträume verschiedener Länge und hält das Ergebnis für eine Veränderung.

Die Spalte, die falsch gelesen fast immer zum falschen Eingriff führt

In pg_stat_activity steht zu jeder Sitzung eine Spalte query. Der naheliegende Schluss — hier steht, was diese Sitzung gerade tut — ist nur in einem von mehreren Fällen richtig.

Wörtlich aus der DokumentationDie Spalte enthält den Text der zuletzt ausgeführten Abfrage dieses Backends. Ist state gleich active, zeigt das Feld die gerade laufende Abfrage. In allen anderen Zuständen zeigt es die zuletzt ausgeführte Abfrage.

Eine Sitzung im Zustand idle zeigt also weiterhin die letzte Abfrage an, die sie ausgeführt hat — vor drei Sekunden oder vor drei Stunden. Wer eine Übersicht nach query filtert und dort ein bedrohlich aussehendes DELETE findet, hat damit nichts über eine laufende Operation belegt. Die Sitzung tut in diesem Moment nichts.

active
Führt gerade eine Abfrage aus. Nur hier beschreibt query etwas Laufendes.
idle
Verbunden, ohne offene Transaktion, tut nichts. query ist Vergangenheit.
idle in transaction
Transaktion offen, aber keine Abfrage aktiv. Der interessante Fall — die Sperren dieser Transaktion bestehen weiter.
idle in transaction (aborted)
Wie oben, aber eine Anweisung der Transaktion hat einen Fehler geworfen. Es kommt nur noch ein Zurückrollen infrage.
disabled
Wird gemeldet, wenn track_activities für dieses Backend abgeschaltet ist. Dann gibt es überhaupt keine Aussage.
Der Zustand, auf den es meist ankommtidle in transaction ist die Zeile, nach der man sucht — nicht active. Eine aktive Abfrage arbeitet; eine offene, untätige Transaktion hält ihre Sperren und blockiert andere, ohne selbst Fortschritt zu machen. Genau das ist der Zustand, den ein Dialogfenster erzeugt, das auf eine Benutzereingabe wartet.
Und der Text ist gekürztDer Abfragetext wird laut Dokumentation voreingestellt bei 1024 Byte abgeschnitten; ändern lässt sich das über track_activity_query_size. Wer eine lange, generierte Abfrage anhand dieser Spalte wiederzuerkennen versucht, vergleicht möglicherweise nur deren identischen Anfang.

Wer was überhaupt sehen darf

Ein Überwachungskonto wird üblicherweise mit möglichst wenig Rechten angelegt. Bei den Statistiksichten führt genau das zu einem Fehlerbild, das aussieht wie ein ruhiges System.

Die dokumentierte EinschränkungGewöhnliche Benutzer sehen alle Angaben nur zu ihren eigenen Sitzungen — also zu Sitzungen einer Rolle, deren Mitglied sie sind. In Zeilen über andere Sitzungen sind viele Spalten null. Dass eine Sitzung existiert und zu welchem Benutzer und welcher Datenbank sie gehört, bleibt sichtbar. Superuser und Rollen mit den Rechten der eingebauten Rolle pg_read_all_stats sehen alles.

Die Folge ist tückisch, weil nichts fehlschlägt. Die Abfrage läuft, liefert Zeilen, und in den Spalten state, query und wait_event steht überall nichts. Ein Dashboard, das daraus „keine aktiven Abfragen“ ableitet, meldet Ruhe, während eine Sperrkette den Dienst anhält. Das ist kein Rechtefehler mit Meldung, sondern ein stiller Sichtbarkeitsfehler.

Kurzcheck

Ein Überwachungskonto meldet dauerhaft null aktive Abfragen, obwohl Anwender von Hängern berichten. Was prüfst du zuerst?

  • Ob das Konto die Rechte hat, fremde Sitzungen überhaupt zu sehen
  • Ob die Abfrage des Dashboards einen Syntaxfehler enthält
  • Ob die Anwender sich auf eine andere Instanz verbinden

Treffer. Richtig. Ohne die entsprechenden Rechte liefert die Sicht Zeilen mit lauter Nullwerten in genau den Spalten, die das Dashboard auswertet — ohne Fehlermeldung.

Statistiken frieren in einer Transaktion ein

Eine zweite Eigenheit trifft jeden, der Statistiken in einem Skript ausliest statt in einer einzelnen Abfrage.

Fragt ein Serverprozess aufgelaufene Statistiken ab, werden die abgerufenen Werte laut Dokumentation in der Voreinstellung bis zum Ende der aktuellen Transaktion zwischengespeichert. Solange die Transaktion läuft, zeigen die Sichten damit unveränderte Zahlen. Dasselbe gilt für die Angaben zu den aktuellen Abfragen aller Sitzungen: Sie werden beim ersten Zugriff innerhalb der Transaktion erhoben und danach unverändert weitergereicht.

Das ist Absicht, kein FehlerDie Dokumentation nennt es ausdrücklich eine Funktion und keinen Fehler: Man kann mehrere Abfragen auf die Statistiken stellen und die Ergebnisse zueinander in Beziehung setzen, ohne dass sich die Zahlen unter der Hand ändern. Wer allerdings in einer Schleife innerhalb einer Transaktion misst, misst mehrfach denselben Moment.

Das Skript, das immer dasselbe Bild liefert

Szenario

Ein Diagnoseskript soll über zwei Minuten hinweg alle fünf Sekunden die aktiven Sitzungen erfassen, um eine Sperrkette einzufangen. Es liefert 24 identische Momentaufnahmen.

Anforderungen

  • Alle Messungen laufen über dieselbe Verbindung
  • Das Skript öffnet zu Beginn eine Transaktion und schließt sie am Ende
  • Zwischen den Messungen wird nichts bestätigt

Schritte

  1. Die erste Messung baut den Schnappschuss auf. Alle folgenden Messungen innerhalb derselben Transaktion bekommen genau diesen Schnappschuss erneut geliefert.
  2. Der einfachste Weg ist, jede Messung in einer eigenen Transaktion laufen zu lassen — im Autocommit also ohne umschließendes BEGIN.
  3. Muss das Skript aus anderen Gründen in einer Transaktion bleiben, verwirft pg_stat_clear_snapshot() den zwischengespeicherten Stand, sodass der nächste Zugriff neu erhebt.
  4. Alternativ steuert stats_fetch_consistency das Verhalten grundsätzlich: Der Wert none verzichtet auf das Zwischenspeichern, wenn ohnehin nur einmal zugegriffen wird.

Merksatz: Eine Messreihe, die sich nicht verändert, ist zuerst ein Verdacht gegen die Messung — nicht ein Befund über das System.

Warten messen statt Auslastung

Die nützlichste Sicht in beiden Systemen ist die auf Wartevorgänge. Sie beantwortet nicht, was beschäftigt war, sondern wodurch Arbeit aufgehalten wurde — und das ist fast immer die Frage, die hinter einer Beschwerde steht.

PostgreSQL führt dazu in pg_stat_activity zwei Spalten: wait_event_type und wait_event. Beide sind null, wenn das Backend gerade auf nichts wartet. Eine Abfrage auf die Zeilen mit gesetztem Wartetyp liefert unmittelbar die Liste dessen, was aufgehalten wird, und woran. Ein Wartetyp Lock etwa verweist auf eine Sperre — also auf eine andere Sitzung, nicht auf ein Gerät.

SQL Server führt die aufgelaufene Variante in sys.dm_os_wait_stats, nach Wartetyp aufsummiert. Für den aktuellen Moment stehen die Spalten wait_type und wait_time in sys.dm_exec_requests — mit einer wichtigen Einschränkung: Sie sind laut Dokumentation nur gefüllt, wenn die Anforderung gerade blockiert ist. Was zuletzt der Grund war, steht getrennt in last_wait_type.

Die Namen sind kryptisch — die Bedeutung steht im KatalogEin Warteereignis heißt relation, WALWriteLock oder DataFileRead, und keiner dieser Namen erklärt sich selbst. PostgreSQL führt dazu die Systemsicht pg_wait_events mit Typ, Name und einer Beschreibung je Ereignis. Die Dokumentation zeigt ausdrücklich, wie man sie über Typ und Name an pg_stat_activity anfügt — damit wird aus einer Abkürzung ein Satz, den man in ein Ticket schreiben kann.
Der Fallstrick bei paralleler AusführungNutzt eine Abfrage mehrere Arbeitsthreads, ist in dieser Sicht laut Dokumentation nur der koordinierende Thread zu sehen, und nur für ihn werden die Wartespalten gefüllt. Einzelne Teilaufgaben können auf verschiedene Dinge warten, und eine Teilaufgabe kann blockiert sein, während andere derselben Anforderung weiterlaufen. Wer das auflösen muss, braucht sys.dm_os_waiting_tasks.

`blocking_session_id` und seine Sonderwerte

Die Spalte blocking_session_id in sys.dm_exec_requests ist der kürzeste Weg zu einer Blockierungskette — und eine Einladung zu einer Alarmregel, die falsch auslöst.

Laut Dokumentation bedeutet null oder 0, dass die Anforderung nicht blockiert ist — oder dass die Angaben zur blockierenden Sitzung nicht verfügbar sind beziehungsweise sich nicht ermitteln lassen. Beides fällt auf denselben Wert zusammen; „nicht blockiert“ und „nicht feststellbar“ sind hier nicht unterscheidbar.

Dazu kommen negative Sonderwerte mit eigener Bedeutung. Die Dokumentation hebt einen davon hervor: Der Wert `-5` bedeutet, dass sich die Sitzung des Sperrbesitzers für diesen Sperrtyp nicht nachverfolgen lässt — und sie stellt ausdrücklich fest, dass -5 für sich genommen kein Leistungsproblem anzeigt. Es weist darauf hin, dass die Sitzung auf den Abschluss eines asynchronen Vorgangs wartet. Je nach Last kann dieser Wert häufig auftreten.

Warum das eine Alarmregel zerstörtEine Regel der Form „melde, wenn blocking_session_id ungleich 0 ist“ feuert damit dauerhaft auf einem gesunden System. Wer den Alarm daraufhin stummschaltet, verliert auch die echten Blockierungen. Die Regel muss die negativen Sonderwerte ausnehmen — und zusätzlich eine Mindestdauer verlangen, weil kurzes Blockieren normaler Betrieb ist.

Das Protokoll als Messinstrument

Was die Momentaufnahme nicht einfängt, weil es zwischen zwei Messungen passiert, fängt das Serverprotokoll ein — vorausgesetzt, es ist dafür eingestellt. Die Voreinstellungen sind hier bewusst zurückhaltend.

log_min_duration_statement
Protokolliert die Dauer jeder Anweisung, die mindestens so lange lief. Voreinstellung ist -1abgeschaltet. Der Wert 0 protokolliert alle Anweisungen.
log_min_duration_sample
Dasselbe, aber nur für einen Teil der Anweisungen; der Anteil steht in log_statement_sample_rate. Gedacht für Lasten, bei denen alles zu protokollieren zu viel wäre.
log_lock_waits
Erzeugt einen Eintrag, wenn eine Sitzung länger als deadlock_timeout auf eine Sperre wartet. Voreinstellung aus.
log_autovacuum_min_duration
Protokolliert Aufräumvorgänge ab dieser Dauer; Voreinstellung 10 Minuten. Meldet zusätzlich, wenn ein Vorgang wegen einer konfliktierenden Sperre übersprungen wurde.
log_checkpoints
Protokolliert Prüfpunkte samt Kennzahlen. Voreinstellung an.
Die Einstellung, die eine Produktion lahmlegen kannlog_min_duration_statement = 0 protokolliert jede einzelne Anweisung mit ihrem vollen Text. Auf einer Instanz mit nennenswerter Last erzeugt das ein Protokollvolumen, das das Dateisystem füllt und die Schreiblast spürbar erhöht — also genau die Wirkung, die man untersuchen wollte. Der sinnvolle Einstieg ist eine Schwelle oberhalb dessen, was normal ist; alles zu protokollieren ist eine Maßnahme für eine Testinstanz.
Die Einstellung, die Fehler auswertbar machtlog_error_verbosity steuert, wie ausführlich eine Meldung im Serverprotokoll steht. Der Wert VERBOSE nimmt laut Dokumentation den SQLSTATE-Fehlercode mit auf. Das ist der Unterschied zwischen einer Protokollzeile, die man lesen muss, und einer, die sich zählen lässt: Serialisierungsfehler tragen immer den Code 40001, und erst mit dem Code im Protokoll lässt sich beantworten, ob ihre Zahl nach einer Umstellung gestiegen ist.

Ein nützlicher Feinunterschied betrifft log_duration: Die Dokumentation hält fest, dass eine überschrittene Schwelle bei log_min_duration_statement den Abfragetext mitprotokolliert, während log_duration das nicht tut. Beides zusammen ergibt darum ein brauchbares Muster für hoch belastete Installationen: alle Dauern erfassen, den Text nur oberhalb der Schwelle.

Vom Signal zur Hypothese

Nutzerwirkung bestätigen, bevor gemessen wirdZeitfenster eingrenzen und den Bezugszeitraum der Zähler mitlesenMomentaufnahme und aufgelaufene Sicht getrennt auswertenAuf Warten schauen, nicht auf AuslastungHypothese formulieren und mit einer zweiten Quelle prüfen
Wirkung vor Kennzahl

Der erste Schritt wird am häufigsten übersprungen und spart am meisten Arbeit. Eine hohe Kennzahl ohne betroffenen Anwender ist kein Vorfall, sondern ein Messwert — und die Schwelle, die ihn ausgelöst hat, gehört dann überprüft statt das System.

Was eine gute Schwelle ausmachtEine Schwelle ist eine Behauptung über Normalität. Ohne Kenntnis des Normalfalls ist jeder Wert geraten — und ein geratener Wert erzeugt entweder Alarme ohne Wirkung oder Stille im Ernstfall. Deshalb gilt die Reihenfolge: erst über einen repräsentativen Zeitraum beobachten, dann die Schwelle setzen, dann sie anhand der Fehlalarme nachziehen. Der Preis ist, dass die ersten Wochen einer neuen Überwachung wenig taugen — das ist unvermeidbar und kein Grund, den Schritt zu überspringen.
AbgrenzungWie eine konkrete Blockierungskette aufgelöst und ein laufender Vorfall geführt wird, steht im Modul über die Diagnose von Datenbankvorfällen. Alarmentwurf, Bereitschaft und Vorfallskoordination als Handwerk stehen im Track monitoring-incident-operations. Hier ging es um die Instrumente der Datenbank selbst — und darum, was sie tatsächlich sagen.

Jetzt anwenden

Diesen Stoff gibt es als Modul mit bewerteten Entscheidungs-Checks — dieselbe Einführung, danach die Übungen.

Zum Modul →
Quellen & Aktualität5 Primärquellen · zuletzt geprüft:
  1. 01postgresql.org/docs/current/monitoring-stats.html
  2. 02postgresql.org/docs/current/runtime-config-logging.html
  3. 03postgresql.org/docs/current/explicit-locking.html
  4. 04learn.microsoft.com/en-us/sql/relational-datab…t-stats-transact-sql
  5. 05learn.microsoft.com/en-us/sql/relational-datab…equests-transact-sql