PRODSouveräne europäische BaaS-PlattformÖffnen Sie das Dashboard →

Leistung · 10 Min. Lesezeit

PgBouncer Transaktionspooling erklärt

Affane Daylami · Fondateur · 12. Juni 2026

Zurück zum Blog

Der Transaktionsmodus von PgBouncer gibt die PostgreSQL-Verbindung am Ende jeder Transaktion frei, nicht wenn der Client die Verbindung trennt. Dies macht es möglich, Tausende von HTTP-Clients mit ein paar Dutzend tatsächlichen Serververbindungen zu bedienen, und es ist der empfohlene Modus für jede REST-API mit kurzen Abfragen.

Dieser englische Text wurde automatisch aus dem französischen Original generiert und wurde noch nicht überprüft.
Diese Seite wurde automatisch übersetzt. Maßgeblich ist die englische Version.

Dieser Gewinn hat einen bestimmten Preis: Der Transaktionsmodus unterbricht stillschweigend alles, was eine stabile Postgres-Verbindung von einer Anfrage zur nächsten voraussetzt. Sitzung SET, LISTEN/NOTIFY, Beratungssperren, Cursor, die die Transaktion überleben, benannte vorbereitete Anweisungen. Dieser Artikel beschreibt den Mechanismus detailliert, listet diese Grenzwerte mit ihren genauen Symptomen auf und zeigt dann, wie ein Backend in der Produktion (unseres, direkt in seinem Repository überprüft) ihn konfiguriert, ohne von ihm gefangen zu werden. Informationen zur Messmethode hinter den hier genannten Leistungszahlen finden Sie in unserer Benchmark-Methodik .

Das Wesentliche

  • Transaktionsmodus: Die Serververbindung wird am Ende jeder Transaktion freigegeben, nicht wenn der Client die Verbindung trennt. Dies ist der effizienteste Modus zum Teilen kurzer Verbindungen vom Typ REST API.
  • Aufgrund der Konstruktion nicht kompatibel mit: Sitzung SET/RESET, LISTEN/NOTIFY, Sitzungshinweissperren, WITH HOLD-Cursor, temporäre Tabellen, die von einer Anforderung zur anderen wiederverwendet werden.
  • Die häufigste Falle in der Praxis: benannte vorbereitete Anweisungen, die mehrere Treiber (sqlx, asyncpg, der JDBC-pgjdbc-Treiber) standardmäßig aktivieren, können auf einer anderen Serververbindung abgespielt werden und unter Last einen Fehler wie prepared statement does not exist auslösen.
  • Seit Version 1.21 kann PgBouncer Protokoll-vorbereiteten Anweisungen im Transaktionsmodus folgen (LRU-Cache über Serververbindung). Dies verhindert nicht, dass der clientseitige Cache deaktiviert wird, wenn Ihre Anwendung search_path bei jeder Anfrage ändert.
  • Im Aurabase-Code verifiziert: Die Mandantenpools laufen mit statement_cache_capacity(0) und PgBouncer in pool_mode=transaction, während PostgREST freiwillig in direkter Verbindung bleibt, um sein Schema über LISTEN/NOTIFY neu zu laden.
#
Konzepte

Die 3 Pooling-Modi von PgBouncer

PgBouncer bietet drei Modi, die sich nur darin unterscheiden, wann die Postgres-Serververbindung zum gemeinsamen Pool zurückkehrt. Die offizielle Dokumentation nennt sie session, transaction und statement (pgbouncer.org/features.html, Abschnitt „Pooling-Modi“, abgerufen am 24. August 2026).

ModeSerververbindung lockerSitzungskompatibilität
Sitzung (Standard)Beim Trennen der Client-VerbindungInsgesamt: SET, LISTEN, Cursors, alles funktioniert wie live
TransaktionAm Ende jeder Transaktion (COMMIT/ROLLBACK)Teilweise: nur das, was für die Transaktion lokal bleibt
AussageNach jeder individuellen AnfrageMinimal: explizite Transaktionen mit mehreren Abfragen sind verboten

Der Sitzungsmodus ist am freizügigsten, bietet jedoch die geringste Skalierbarkeit: Eine Postgres-Verbindung bleibt für einen Client reserviert, solange er verbunden bleibt, auch wenn zwischen zwei Anforderungen nichts unternommen wird. Der Anweisungsmodus ist für ganz bestimmte Fälle reserviert (Nur-Lese-Proxy, Gesundheitsprüfungen) und unterbricht sogar klassische explizite Transaktionen. Der Transaktionsmodus ist der in der Praxis vorherrschende Kompromiss für eine REST-API: Jede HTTP-Anfrage entspricht im Allgemeinen einer einzelnen kurzen Postgres-Transaktion.

#
Mechanismus

So funktioniert der Transaktionsmodus, Verbindung für Verbindung

Im Transaktionsmodus stellt PgBouncer nur dann eine Serververbindung zu einem Client her, wenn dieser eine Transaktion öffnet, und gibt sie bei COMMIT oder ROLLBACK an den Pool zurück. Zwischen zwei Transaktionen kann es vorkommen, dass derselbe Client einer völlig anderen Serververbindung zugewiesen wird.

Konkret kann PgBouncer mit einem default_pool_size von 20 mehrere hundert gleichzeitige Kunden aufnehmen, bei denen zu einem bestimmten Zeitpunkt nur wenige Transaktionen tatsächlich ausgeführt werden. Es ist dieses Verhältnis, das den Transaktionsmodus für eine REST-API mit hohem Datenverkehr, aber kurzen Transaktionen rechtfertigt: Die seltene Ressource (eine Postgres-Verbindung, teuer im Speicher auf der Serverseite) wird nur für die unbedingt erforderliche Zeit belegt.

pgbouncer.iniini
[pgbouncer]
listen_port = 5432

; La connexion serveur est libérée dès la fin de chaque transaction
pool_mode = transaction

default_pool_size = 80
max_client_conn = 1000

; Ne pas utiliser avec des sessions stateful (SET, advisory locks, LISTEN/NOTIFY)

Dieser letzte Kommentar fasst das Wesentliche zusammen: Der Transaktionsmodus funktioniert, weil er absichtlich die Verbindung zwischen „meiner Anwendungssitzung“ und „meiner Postgres-Verbindung“ unterbricht. Alles, was auf diesem Link basiert, ist kaputt. Im nächsten Abschnitt wird genau aufgeführt, was.

#
Grenzen

Was bricht im Transaktions-Pooling-Modus zusammen?

In der offiziellen PgBouncer-Dokumentation werden PostgreSQL-Funktionen explizit aufgeführt, die ihre Bedeutung verlieren, sobald eine Serververbindung zwischen zwei Anfragen desselben Clients wiederverwendet werden kann.

Betroffene FunktionalitätWarum geht es kaputt?Typisches Symptom
SITZUNG EINSTELLEN / EINSTELLENDie Einstellung gilt für eine Verbindung, die unmittelbar danach wiederverwendet werden kannEin Parameter scheint zwischen zwei Anfragen zufällig vergessen worden zu sein
ZUHÖREN/BENACHRICHTIGENSetzt eine dauerhafte Verbindung voraus, um Benachrichtigungen zu empfangenDer Kunde wird nie oder nur zeitweise benachrichtigt
SitzungshinweissperrenDie Sperre wird von der Serververbindung gehalten, nicht vom logischen ClientEine Sperre wird vor dem erwarteten Abschluss freigegeben oder wird nie freigegeben
MIT HOLD-SchiebereglernMuss über die Transaktion hinaus, die es eröffnet hat, überlebenFehler „Cursor existiert nicht“ bei der nächsten Iteration
Temporäre TabellenBezieht sich auf die Postgres-Sitzung, nicht auf die TransaktionDie Tabelle „verschwindet“ bei der nächsten Abfrage
Vorbereitete Aussagen benanntAuf einer bestimmten Serververbindung vorbereitet, auf einer anderen wiedergegeben„Vorbereitete Anweisung ... existiert nicht“ unter Last
Die Falle ist nicht immer unmittelbar

Die meisten dieser Einschränkungen machen sich in der lokalen Entwicklung nicht bemerkbar, wo typischerweise eine einzige Verbindung den gesamten Datenverkehr bedient. Sie treten unter realer Last auf, wenn tatsächlich mehrere Clients den Pool teilen und eine Serververbindung zwischen zwei Anfragen desselben logischen Clients tatsächlich den Besitzer wechselt. Ein Rauchtest bringt sie fast nie ans Licht.

#
Gemeinsame Falle

Vorbereitete Aussagen: die am meisten missverstandene Grenze

Die meisten modernen Postgres-Treiber bereiten benannte Anfragen standardmäßig auf der Protokollseite vor, ohne dass der Anwendungscode dies explizit anfordert. Genau das macht es schwierig, diese Falle vorherzusehen.

Eine vom Protokoll vorbereitete Anweisung wird zum Zeitpunkt von Parsebenannt und auf einer bestimmten Serververbindung zwischengespeichert. Im Transaktionsmodus kann diese Verbindung zwischen zwei Anfragen desselben logischen Clients einem anderen Client zugewiesen werden. Wenn der Treiber dann denselben Anweisungsnamen auf einer Verbindung wiedergibt, für die er nie vorbereitet wurde, antwortet Postgres mit einem expliziten Fehler, normalerweise prepared statement "sqlx_s_N" does not exist für einen SQLX-Client. Das Verhalten ist sporadisch: Es hängt von der Leistung der Verbindungen unter Last ab und nicht von einem deterministischen Fehler, der bei jedem Aufruf reproduzierbar ist.

Die clientseitige Korrektur ist unabhängig von der Sprache dieselbe: Deaktivieren Sie den Cache benannter vorbereiteter Anweisungen oder erzwingen Sie unbenannte Abfragen für jeden Pool, der einen Pooler im Transaktionsmodus kreuzt. In Rust mit sqlx geht es bei den Verbindungsoptionen über statement_cache_capacity(0).

pool.rsrust
let connect_options = url
    .parse::<PgConnectOptions>()?
    .statement_cache_capacity(0);

// Äquivalente: asyncpg -> Statement_cache_size=0, pgjdbc -> PrepareThreshold=0

Seit Version 1.21 entschärft PgBouncer einen Teil des Problems auf der Serverseite: Es kann protokollvorbereitete Anweisungen im Transaktionsmodus verfolgen und sie im laufenden Betrieb auf der zugewiesenen Verbindung vorbereiten, wobei pro Verbindung ein LRU-Cache vorhanden ist, dessen Größe über max_prepared_statementsangepasst wird. Dies verringert die Anzahl der Fehler, befreit Sie jedoch nicht davon, den Client-Cache in einem Pool mit mehreren Mandanten zu deaktivieren, in dem sich search_path von einer Anforderung zur anderen ändert: Ein zwischengespeicherter Plan friert die interne Kennung (OID) der aufgelösten Tabelle zum Zeitpunkt von Parseein, und die Wiedergabe unter einem anderen Schema gibt möglicherweise Daten vom falschen Mandanten zurück und nicht einen einfachen Fehler.

#
Code eingecheckt

So konfiguriert Aurabase PgBouncer im Transaktionsmodus

Das Aurabase-Repository stellt PgBouncer als pool_mode=transaction vor der gemeinsamen Datenebene (deploy/helm/aurabase/templates/infra/pgbouncer.yaml) und ein identisch konfiguriertes Pooler CNPG vor jeder dedizierten Postgres-Instanz eines Mandanten (deploy/cnpg/tenant-pooler.yaml) bereit. Beide Wege wenden die gleiche oben beschriebene Disziplin an.

Der Quellcode dokumentiert einen spezifischen Sicherheitsgrund für diese Wahl, nicht nur einen Stabilitätsgrund. Von Mietern gemeinsam genutzte Postgres-Pools positionieren bei wiederverwendeten Verbindungen einen anderen search_path pro Projekt. Eine zwischengespeicherte vorbereitete Anweisung friert die OID der aufgelösten Tabelle zum Zeitpunkt von Parseein; Die Wiedergabe für einen anderen Mandanten auf derselben Verbindung würde die Abfrage anhand des Schemas des ersten Mandanten ausführen, was eine Umgehung der Isolation und nicht nur einen Anwendungsfehler darstellt. statement_cache_capacity(0) wird daher ausnahmslos angewendet, auch auf dedizierten Instanzen, die auch im Transaktionsmodus einen CNPG-Pooler durchlaufen.

Zweite Sitzungshygienemaßnahme: Wenn jede Verbindung zum Pool zurückkehrt, führt ein Hook DISCARD ALL aus (Einstellungen zurücksetzen, vorbereitete Anweisungen auf der Serverseite freigeben, Beratungssperren freigeben, Cursor und temporäre Tabellen löschen). Ohne diesen Hook könnte ein Sitzungsrest, der von einer vorherigen Anfrage stammt, bei der nächsten Anfrage eines anderen Mandanten verloren gehen, der dieselbe recycelte Verbindung wiederverwendet.

Ausnahme akzeptiert: Dedizierte PostgREST-Instanzen bleiben in direkter Verbindung mit der Primärinstanz, ohne den Pooler zu durchlaufen. Das Neuladen des PostgREST-Schemas basiert auf LISTEN/NOTIFY, das eine dauerhafte Verbindung voraussetzt, genau die Funktionalität, die der Transaktionsmodus unterbricht (Details wurden bereits in unserem Artikel über PostgREST-Kompatibilität bei Aurabasedokumentiert). RLS-Einstellungen pro Anfrage werden innerhalb einer expliziten Transaktion an SET LOCAL übergeben. Dies ist die einzige Möglichkeit, mit einem Pool kompatibel zu bleiben, der Serververbindungen bei jedem COMMIT ändern kann (siehe unseren Artikel übermehrinstanzenfähige RLS-Isolation).

#
Praktischer Leitfaden

Aktivieren Sie den Transaktionsmodus, ohne Ihre Anwendung zu beschädigen

Eine kurze Checkliste, die für jedes Backend gilt, das von einer direkten Postgres-Verbindung zu einem PgBouncer im Transaktionsmodus wechselt.

  1. Überprüfen Sie den Anwendungscode. Suchen Sie nach Nicht-Transaktionssperren SET, LISTEN/NOTIFY, Sitzungshinweissperren, WITH HOLD Cursorn und temporären Tabellen, die zwischen Abfragen wiederverwendet werden.
  2. Ersetzen Sie Sitzungs-SETs durch LOKALE-SETs innerhalb einer expliziten Transaktion. Dies ist die einzige Einstellung, die das Verbindungsrecycling ordnungsgemäß übersteht, da sie bei COMMIT/ROLLBACK bereinigt wird und nicht bei der nächsten Verbindung verloren geht.
  3. Deaktivieren Sie den treiberseitigen Cache für vorbereitete Anweisungen, wenn Ihr Pool den Pooler durchläuft und sich das Schema oder die Rolle von einer Anforderung zur anderen ändert. Die Leistungskosten sind real, aber messbar und viel geringer als das Risiko von Verlusten zwischen Mietern.
  4. Isolieren Sie Verbindungen, die wirklich den Sitzungsmodus benötigen (Migrationen, Admin-Skripte, alles, was von LISTEN/NOTIFY abhängt), auf eine direkte Nicht-Pooler-Verbindung, anstatt auf den Transaktionsmodus für den gesamten anderen Datenverkehr zu verzichten.
  5. Größe default_pool_size und max_client_conn relativ zum tatsächlichen Postgres max_connections, nicht durch eine beliebige Zahl, die aus einem anderen Projekt kopiert wurde.
  6. Test unter realer Last, nicht nur Rauchtest. Vorbereitete Anweisungsfehler und Sitzungseinstellungslecks treten fast nie bei einer einzelnen lokalen Verbindung auf.
  7. Überwachen Sie SHOW POOLS und SHOW STATS von der PgBouncer-Verwaltungskonsole aus, sobald sie in der Produktion sind, um die Poolsättigung zu erkennen, bevor sie auf der Clientseite sichtbar wird.
#
Entscheidung

Sollten Sie immer den Transaktionsmodus statt der Sitzung wählen?

Nein, aber es ist die richtige Standardauswahl für die überwiegende Mehrheit der REST-APIs. Der Sitzungsmodus ist nach wie vor vorzuziehen für eine Legacy-Anwendung, die stark von Sitzungsfunktionen abhängt, die Sie nicht schnell umgestalten können, oder für geringen Datenverkehr, bei dem der Pooling-Gewinn den Migrationsaufwand nicht ausgleicht.

PgBouncer ist auch nicht die einzige Implementierung dieses Pooling-Modells: Supavisor (Supabase) und PgCat sind zwei aktuelle Alternativen mit unterschiedlichen Kompromissen bei Lastverteilung und Clustering. Sehen Sie sich unseren detaillierten Vergleich an, PgBouncer vs Supavisor vs PgCat, um je nach Topologie zwischen den dreien zu wählen.

#
Häufig gestellte Fragen

FAQs

Die Fragen, die am häufigsten auftauchen, wenn der Transaktionsmodus in der Produktion aktiviert wird.

Was ist der PgBouncer-Transaktionspooling-Modus?+
Dies ist einer der drei Modi von PgBouncer (mit Sitzung und Anweisung), bei dem die Postgres-Verbindung am Ende jeder Transaktion einem anderen Client zugewiesen wird und nicht erst, wenn der Client die Verbindung trennt. Dadurch ist es möglich, viel mehr konkurrierende Kunden zu bedienen, als tatsächlich offene Postgres-Verbindungen vorhanden sind.
Warum stürzen meine vorbereiteten Kontoauszüge im Transaktionsmodus ab?+
Eine benannte vorbereitete Anweisung wird auf einer bestimmten Serververbindung vorbereitet. Im Transaktionsmodus kann diese Verbindung zwischen zwei Anfragen einem anderen Client zugewiesen werden. Wenn Ihr Treiber den Namen der Anweisung auf einer Verbindung wiedergibt, für die er noch nie vorbereitet wurde, gibt Postgres einen Fehler zurück, der besagt, dass die vorbereitete Anweisung nicht vorhanden ist. Die Korrektur besteht darin, den Cache für vorbereitete Anweisungen auf der Treiberseite zu deaktivieren (statement_cache_capacity(0) mit sqlx, Statement_cache_size=0 mit asyncpg).
Können wir LISTEN/NOTIFY hinter einem PgBouncer im Transaktionsmodus verwenden?+
Nein, nicht zuverlässig. LISTEN/NOTIFY setzt eine dauerhafte Verbindung zum Empfang von Benachrichtigungen voraus, was im Transaktionsmodus nicht garantiert wird. Standardmäßig werden Komponenten, die von LISTEN/NOTIFY abhängen (z. B. PostgREST), über eine direkte Verbindung zu Postgres außerhalb des Poolers weitergeleitet.
Sollten wir im Transaktionsmodus SET LOCAL anstelle von SET verwenden?+
Ja, systematisch für jede Einstellung, die für eine bestimmte Anfrage gelten muss. SET LOCAL wird bei COMMIT oder ROLLBACK automatisch bereinigt, sodass es bei einer Serververbindung, die sich zwischen zwei Transaktionen ändern kann, sicher ist. Ein klassischer SET kann an den nächsten Client weitergegeben werden, der die gleiche wiederhergestellte Serververbindung wiederherstellt.
Funktioniert der Transaktionsmodus mit Row Level Security (RLS)?+
Ja, vorausgesetzt, dass die von Ihren RLS-Richtlinien verwendeten JWT-Ansprüche oder Sitzungsvariablen im LOCAL SET innerhalb der Transaktion und nicht im Session SET festgelegt sind. Dies ist das Muster, das in unserem Artikel zur mehrinstanzenfähigen RLS-Isolation beschrieben wird.
PgBouncer, Supavisor, PgCat: Welches soll ich wählen?+
Die drei implementieren ein ähnliches Pooling-Modell mit Unterschieden im Clustering, der Lastverteilung und dem Ökosystem (Supavisor wird von Supabase entwickelt, PgCat ist in Rust geschrieben). Die Wahl hängt vor allem von Ihrer Bereitstellungstopologie und Ihren bestehenden betrieblichen Einschränkungen ab: Weitere Informationen finden Sie in unserem speziellen Vergleich.

BEREIT ZUM EINSATZ?

Ihr Backend in fünf Minuten.

Keine Kreditkarte erforderlich · 500 MB kostenlos · 50.000 MAU