Zwölf Sekunden für 3.000 Zeilen. Hoppla
Zwei Dinge haben mich diese Woche gerettet: EXPLAIN ANALYZE und SET LOCAL enable_nestloop = off. Das Erste ist Postgres’ Art, dir zu zeigen, was der Query Planner tatsächlich getan hat. Nicht, was er zu tun glaubt, sondern echte Ausführungszeiten, echte Zeilenzahlen, echte Join-Strategien. Das Zweite ist ein Override auf Transaktionsebene, der dem Planner sagt: „Für diese Query darfst du keine Nested-Loop-Joins benutzen.“ Du packst es in einen BEGIN/COMMIT-Block, es gilt für genau diese eine Ausführung und sickert nicht in irgendetwas anderes auf der Verbindung. Es ist hässlich. Es ist ein stumpfes Werkzeug. Es hat mir einen 16-fachen Speedup bei einer Query gebracht, bei der drei immer raffiniertere SQL-Umschreibungen alles jeweils schlimmer gemacht haben.
Das Problem: Der WindowAgg-Knoten von Postgres, der Teil, der dein ROW_NUMBER(), RANK(), SUM() OVER (...) ausführt, reicht keine Kardinalitätsschätzungen weiter. Der Planner wertet die Ausgabe der Window Function aus, gerät in Panik und schätzt rows=1. Sitzt diese Window Function in einer View, die in nachgelagerte Joins fließt, zieht diese eine schlechte Schätzung durch jede folgende Join-Entscheidung.
Der Planner wählt Nested Loops, weil Nested Loops für eine Zeile optimal sind. Du hast aber keine eine Zeile. Du hast Tausende. Jede Iteration scannt eine View mit mehreren tausend Zeilen erneut. Es entstehen Millionen Zwischenzeilen, die sofort wieder verworfen werden. Deine Query braucht 12 Sekunden für 3.000 Quellzeilen, und du verlierst eine Stunde deines Lebens mit Umschreibungen, die alle scheitern, weil der zugrunde liegende Schätzfehler jede Query-Form vergiftet, die du ihm vorwirfst. SET LOCAL enable_nestloop = off erzwingt Hash Joins, die in unter einer Sekunde durch die echten Daten pflügen. Es ist eine bekannte Einschränkung ohne saubere Umgehung bei gemanagtem Postgres. Manchmal ist die pragmatische Lösung die richtige.