PostgreSQL-EXPLAIN-ANALYZE-Ausgabe richtig lesen

EXPLAIN zeigt, was der Planer zu tun beabsichtigt; EXPLAIN ANALYZE führt die Abfrage aus und zeigt, was tatsächlich passiert ist. Die Ausgabe gut zu lesen ist die Fähigkeit mit dem größten Hebel in der PostgreSQL-Performance-Arbeit — und sie hat ein paar Fallen, in die selbst erfahrene Entwickler tappen.

Keine Lust, die Rechnerei von Hand zu machen? Fügen Sie Ihren Plan in den EXPLAIN Visualizer ein — er berechnet die exklusiven Zeiten pro Knoten und markiert die häufigen Probleme automatisch, direkt in Ihrem Browser.

Die Anatomie eines Planknotens

Jede Zeile eines Plans ist ein Knoten in einem Baum. Zeilen fließen von den innersten (am stärksten eingerückten) Knoten nach oben. Ein typischer Knoten sieht so aus:

Index Scan using orders_customer_idx on orders
    (cost=0.43..152.80 rows=42 width=98)
    (actual time=0.031..0.512 rows=38 loops=1)

Zwei Gruppen von Zahlen, und sie bedeuten Unterschiedliches:

Falle #1: alles ist pro Schleife

Wenn ein Knoten auf der inneren Seite eines Nested Loop sitzt, kann er tausendfach ausgeführt werden. PostgreSQL meldet seine actual time und rows als Durchschnitt pro Ausführung, wobei loops Ihnen sagt, wie viele Ausführungen stattfanden:

Index Scan using items_order_idx on items
    (actual time=0.005..0.021 rows=3 loops=12000)

Dieser Knoten gab nicht 3 Zeilen in 0,021 ms zurück. Er gab ungefähr 36.000 Zeilen zurück und verbrauchte insgesamt etwa 250 ms (0,021 × 12.000). Multiplizieren Sie immer mit loops, bevor Sie entscheiden, ob ein Knoten günstig ist.

Falle #2: Zeiten sind kumulativ

Die actual time eines Knotens schließt alle seine Kindknoten ein. Wenn ein Sort 900 ms meldet und der Seq Scan darunter 850 ms meldet, kostete die Sortierung selbst nur ~50 ms. Um herauszufinden, wo die Zeit wirklich verbraucht wird, brauchen Sie die exklusive Zeit jedes Knotens: seine Gesamtzeit minus der Gesamtzeiten seiner Kindknoten. Genau das ist die Rechnerei, die in einem 40-Knoten-Plan mühsam wird — und der Hauptgrund, warum Plan-Visualizer existieren.

Falle #3: geschätzte vs. tatsächliche Zeilen

Die wertvollste Diagnose in der gesamten Ausgabe ist die Lücke zwischen geschätzten und tatsächlichen Zeilen. Der Plan wurde auf Basis der Schätzung gewählt — wenn der Planer 40 Zeilen erwartete und 400.000 bekam, wurde jede Entscheidung stromabwärts dieses Knotens (Join-Strategie, Speicherdimensionierung, Index- vs. sequenzieller Scan) auf falschen Annahmen getroffen.

Wenn Sie eine große Abweichung sehen:

Nutzen Sie BUFFERS. Immer.

EXPLAIN (ANALYZE, BUFFERS) fügt eine Zeile wie diese hinzu:

Buffers: shared hit=1520 read=8943 dirtied=12

Buffer-Zahlen erklären den Unterschied zwischen „schnell in meiner Session, langsam in der Produktion": derselbe Plan ist um Größenordnungen langsamer, wenn seine Blöcke nicht gecacht sind. Sie machen auch Bloat sichtbar — eine Abfrage, die 50.000 Blöcke liest, um 100 schmale Zeilen zurückzugeben, liest überwiegend toten Raum oder scannt weit mehr, als sie sollte.

Weitere Ausgaben, die man kennen sollte

Eine Lese-Checkliste

  1. Finden Sie die gesamte Execution Time am Ende — das ist Ihr Budget.
  2. Gehen Sie den Baum nach Knoten durch, deren exklusive Zeit (Gesamt minus Kindknoten, mal Schleifen) einen großen Anteil daran ausmacht. Meist dominieren 1–3 Knoten.
  3. Vergleichen Sie für jeden heißen Knoten geschätzte vs. tatsächliche Zeilen. Eine 10×-Lücke ist ein Planer-Problem, bevor es ein Hardware-Problem ist.
  4. Prüfen Sie BUFFERS: liest er weit mehr Daten, als das Ergebnis rechtfertigt?
  5. Erst dann denken Sie über Abhilfen nach: Statistiken, Indexe, Query-Form, work_mem — ungefähr in dieser Reihenfolge der Wahrscheinlichkeit.
🔍 Verwandte Lektüre: den richtigen Index wählen, wenn der Plan einen vermeidbaren Scan zeigt, und VACUUM & Bloat, wenn Buffer-Zahlen für die zurückgegebenen Zeilen aufgebläht wirken.
🧯 Verwandte Fehler: canceling statement due to statement timeout — der übliche Grund, warum ein Plan untersucht wird — und out of shared memory, wenn ein Plan Tausende von Partitionen berührt.