Persistente Datenbankverbindungen

Was sind persistente Verbindungen?

Persistente Verbindungen sind Verbindungen, die nach Beendigung der Skriptausführung nicht geschlossen werden. Wird eine persistente Verbindung angefordert, prüft PHP, ob bereits eine identische persistente Verbindung (die von einem früheren Aufruf offen geblieben ist) existiert; ist dies der Fall, wird sie wiederverwendet, andernfalls wird eine neue Verbindung aufgebaut. Eine "identische" Verbindung ist eine Verbindung, die zum gleichen Host mit demselben Benutzernamen und Passwort (sofern zutreffend) hergestellt wurde.

Es gibt keine Möglichkeit, eine bestimmte Verbindung anzufordern oder zu garantieren, ob die zurückgegebene Verbindung eine bestehende oder eine ganz neue ist (etwa wenn alle bestehenden Verbindungen in Verwendung sind oder die Anfrage von einem anderen Worker bearbeitet wird, der über einen eigenen Verbindungs-Pool verfügt).

Die persistenten Verbindungen von PHP können daher beispielsweise nicht verwendet werden, um:

  • eine bestimmte Datenbanksitzung einem bestimmten Web-Benutzer zuzuweisen
  • eine umfangreiche Transaktion über mehrere Anfragen hinweg aufzubauen
  • eine Abfrage in einer Anfrage zu starten und die Ergebnisse in einer anderen abzurufen

Persistente Verbindungen bieten keinerlei Funktionalität, die nicht auch mit nicht-persistenten Verbindungen möglich wäre.

Web-Anfragen

Es gibt zwei Möglichkeiten, wie ein Webserver PHP zur Generierung von Webseiten einsetzen kann:

Die erste Methode ist, PHP als CGI-"Wrapper" zu benutzen. Wenn diese Methode eingesetzt wird, wird für jede Anfrage nach einer PHP-Seite vom Webserver eine Instanz des PHP-Interpreters gestartet und anschließend wieder beendet. Durch die Beendigung des Interpreters nach jeder Anfrage werden alle Ressourcen, auf die zugegriffen wurde (wie beispielsweise eine Verbindung zu einem SQL-Datenbankserver), wieder geschlossen. In diesem Fall erreicht man nichts, wenn man persistente Verbindungen benutzt - Persistenz ist in diesem Fall nicht gegeben.

Die zweite und populärste Methode ist der Einsatz von PHP-FPM oder von PHP als Modul in einem Multiprozess-Webserver (derzeit nur Apache). Solche Umgebungen haben typischerweise einen Prozess (den Elternprozess), der eine Reihe weiterer Prozesse (seine Kinder) koordiniert, welche die eigentliche Arbeit des Bereitstellens der Webseiten übernehmen. Wenn eine Anfrage von einem Client eingeht, wird sie an eines der Kinder weitergereicht, das gerade keinen anderen Client bedient. Das bedeutet, dass eine zweite Anfrage desselben Clients an den Server unter Umständen von einem anderen Kindprozess als die erste Anfrage bearbeitet wird. Wurde eine persistente Verbindung einmal geöffnet, kann jede danach vom selben Kindprozess bediente Seite die bereits aufgebaute Verbindung zum SQL-Server weiterverwenden.

Hinweis:

Die verwendete Methode lässt sich anhand des Werts von "Server API" in der Ausgabe von phpinfo() oder anhand des Werts der Konstante PHP_SAPI, ausgeführt über eine Web-Anfrage, prüfen.

Lautet die Server API "Apache 2 Handler" oder "FPM/FastCGI", werden persistente Verbindungen über Anfragen hinweg wiederverwendet, die vom selben Worker bedient werden. Bei jedem anderen Wert bleiben persistente Verbindungen nach jeder Anfrage nicht erhalten.

Kommandozeilen-Prozesse

Da PHP auf der Kommandozeile für jedes Skript einen neuen Prozess verwendet, werden persistente Verbindungen nicht zwischen Kommandozeilen-Skripten geteilt. Es bringt daher keinen Nutzen, sie in kurzlebigen Skripten wie Cronjobs oder einmaligen Kommandozeilenbefehlen einzusetzen. Sie können jedoch zum Beispiel in einem langlaufenden Anwendungsserver nützlich sein, der viele Anfragen oder Aufgaben bedient, von denen jede ihre eigene Datenbankverbindung benötigen kann.

Wozu sie verwenden?

Persistente Verbindungen sind nützlich, wenn der Aufwand zum Aufbau einer Verbindung zu einem SQL-Server hoch ist. Ob dieser Aufwand signifikant ist, hängt von vielen Faktoren ab, etwa von der Art der Datenbank, davon, ob sie auf demselben Rechner wie der Webserver läuft, und davon, wie stark dieser Rechner ausgelastet ist. Ist der Verbindungsaufwand hoch, können persistente Verbindungen erheblich helfen: Jeder Kindprozess verbindet sich nur einmal während seiner gesamten Lebensdauer, statt jedes Mal, wenn er eine Seite verarbeitet, die eine Verbindung zum SQL-Server benötigt. Das bedeutet, dass jeder Kindprozess, der eine persistente Verbindung öffnet, seine eigene Verbindung zum Server unterhält. Bei beispielsweise 20 verschiedenen Kindprozessen, die jeweils ein Skript ausführen, das eine persistente Verbindung zum SQL-Server aufbaut, ergeben sich 20 separate Verbindungen zu diesem Server, eine pro Kind.

Mögliche Nachteile: Verbindungslimits

Es ist jedoch zu beachten, dass dies Nachteile haben kann, wenn eine Datenbank mit Verbindungslimits verwendet wird, die durch persistente Verbindungen der Kindprozesse überschritten werden. Hat die Datenbank ein Limit von 16 gleichzeitigen Verbindungen und versuchen während einer stark ausgelasteten Server-Sitzung 17 Kindprozesse, eine Verbindung herzustellen, wird einer von ihnen fehlschlagen. Bestehen in den Skripten Fehler, die das Schließen der Verbindungen verhindern (wie etwa Endlosschleifen), können die verfügbaren 16 Verbindungen schnell aufgebraucht sein.

Persistente Verbindungen erhöhen in der Regel die Anzahl der zu einem gegebenen Zeitpunkt geöffneten Verbindungen, da untätige Worker weiterhin die Verbindungen halten, die sie für vorherige Anfragen geöffnet haben. Werden viele Worker hochgefahren, um eine Lastspitze zu bewältigen, bleiben die von ihnen geöffneten Verbindungen bestehen, bis der Worker beendet wird oder der Datenbankserver die Verbindung schließt.

Es ist sicherzustellen, dass die vom Datenbankserver erlaubte maximale Anzahl an Verbindungen größer ist als die maximale Anzahl der Worker für Web-Anfragen (zuzüglich aller weiteren Verwendungen wie Cronjobs oder administrativer Verbindungen).

Es empfiehlt sich, in der Dokumentation der Datenbank nachzuschlagen, wie der Datenbankserver aufgegebene oder im Leerlauf befindliche Verbindungen per Timeout behandelt. Lange Timeouts können die Anzahl der zu einem Zeitpunkt geöffneten persistenten Verbindungen erheblich erhöhen.

Mögliche Nachteile: Verwaltung des Verbindungszustands

Manche Datenbank-Erweiterungen führen eine automatische Bereinigung durch, wenn die Verbindung wiederverwendet wird; andere überlassen diese Aufgabe dem Anwendungsentwickler. Je nach gewählter Datenbank-Erweiterung und Anwendungsdesign kann eine manuelle Bereinigung vor Beendigung des Skripts erforderlich sein. Änderungen, die Verbindungen in einem unerwarteten Zustand zurücklassen können, sind unter anderem:

  • Ausgewählte/voreingestellte Datenbank
  • Tabellensperren
  • Nicht abgeschlossene Transaktionen
  • Temporäre Tabellen
  • Verbindungsspezifische Einstellungen oder Funktionen wie Profiling

Tabellensperren und Transaktionen, die nicht aufgeräumt oder abgeschlossen werden, können dazu führen, dass andere Abfragen unbegrenzt blockiert werden und/oder dass eine spätere Wiederverwendung der Verbindung unerwartete Änderungen verursacht.

Ist die falsche Datenbank ausgewählt, kann eine spätere Wiederverwendung der Verbindung Abfragen nicht wie erwartet ausführen (oder führt sie auf der falschen Datenbank aus, wenn sich die Schemata ausreichend ähneln).

Werden temporäre Tabellen nicht aufgeräumt, können nachfolgende Anfragen dieselbe Tabelle nicht erneut anlegen.

Die Bereinigung kann mithilfe von Klassen-Destruktoren oder register_shutdown_function() umgesetzt werden. Dedizierte Connection-Pooling-Proxies, die dies als Teil ihrer Funktionalität enthalten, können ebenfalls in Betracht gezogen werden.

Schlussbemerkungen

Aufgrund des oben beschriebenen Verhaltens und der potenziellen Nachteile sollten persistente Verbindungen nicht ohne sorgfältige Abwägung eingesetzt werden. Sie sollten nicht ohne zusätzliche Anpassungen der Anwendung sowie ohne sorgfältige Konfiguration von Datenbankserver und Webserver und/oder PHP-FPM verwendet werden.

Es sollten alternative Lösungen in Betracht gezogen werden, etwa die Untersuchung und Behebung der Ursachen für den Verbindungsaufbau-Overhead (beispielsweise das Deaktivieren von Reverse-DNS-Lookups auf dem Datenbankserver) oder dedizierte Connection-Pooling-Proxies.

Für Web-APIs mit hohem Anfragevolumen sollten alternative Runtimes oder langlaufende Anwendungsserver in Betracht gezogen werden.