Connection-Pooling in MySQL richtig konfigurieren
AI generated
InnoDB
SQL
MySQL · PHP-FPM · ProxySQL · Infrastruktur
Connection-Pooling richtig konfigurieren
Grenzen und Timeouts, die zur Anwendung passen

Ein falsch dimensioniertes Connection-Pooling führt entweder zu Too-many-connections-Fehlern unter Last oder zu verschwendeten Ressourcen durch tausende ungenutzte Leerlaufverbindungen. Dieser Artikel zeigt, wie max_connections richtig dimensioniert wird, wie wait_timeout und interactive_timeout zusammenspielen, wie PHP-FPM-Pool-Größe und MySQL-Verbindungslimit aufeinander abgestimmt werden und wann ein externer Connection Pooler wie ProxySQL sinnvoll ist.

18 Min. Lesezeit max_connections · wait_timeout · ProxySQL MySQL 8.0 · PHP-FPM · PDO

1. Warum jede TCP-Verbindung zu MySQL kostet

Jede neue Verbindung zu MySQL erfordert einen TCP-Handshake, eine Authentifizierung und die Allokation von Speicher für Session-Buffer, Thread-Stack und Verbindungsstatus auf dem Server. Ohne Connection-Pooling öffnet jede eingehende Anfrage einer Webanwendung eine neue Verbindung und schließt sie am Ende der Anfrage wieder, was bei hoher Anfragefrequenz zu spürbarem Overhead führt, noch bevor die eigentliche Datenbankabfrage überhaupt ausgeführt wird.

Connection-Pooling löst dieses Problem, indem bereits geöffnete Verbindungen wiederverwendet werden, statt sie für jede Anfrage neu aufzubauen. Der Effekt ist doppelt: Die Latenz pro Anfrage sinkt, weil der Verbindungsaufbau entfällt, und der MySQL-Server muss weniger gleichzeitige Verbindungen verwalten, weil sich viele Anwendungsprozesse einen kleineren Pool an dauerhaften Verbindungen teilen. Die folgenden Abschnitte zeigen, wie die relevanten Parameter auf Server- und Anwendungsseite aufeinander abgestimmt werden.

2. max_connections richtig dimensionieren

Der Serverparameter max_connections begrenzt die Anzahl gleichzeitiger Verbindungen zum MySQL-Server. Der Standardwert von 151 ist für viele produktive Setups zu niedrig, gerade wenn mehrere Anwendungsserver oder PHP-FPM-Pools gleichzeitig auf dieselbe Datenbank zugreifen. Wird der Grenzwert erreicht, lehnt MySQL neue Verbindungen mit dem Fehler ERROR 1040: Too many connections ab, was in der Anwendung zu sichtbaren Fehlern für Endnutzer führt.

Eine zu hohe Einstellung von max_connections ist allerdings ebenfalls riskant: Jede offene Verbindung reserviert Speicher, typischerweise mehrere Megabyte je nach Session-Buffer-Konfiguration, sodass tausende gleichzeitige Verbindungen den verfügbaren Arbeitsspeicher des Servers erschöpfen können, lange bevor die CPU an ihre Grenzen stößt. Die sinnvolle Obergrenze ergibt sich aus verfügbarem RAM geteilt durch den Speicherbedarf pro Verbindung, nicht aus einer pauschalen hohen Zahl.


-- Check current connection limit and usage
SHOW VARIABLES LIKE 'max_connections';
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Max_used_connections';

-- Estimate per-connection memory footprint
SHOW VARIABLES LIKE 'thread_stack';
SHOW VARIABLES LIKE 'sort_buffer_size';
SHOW VARIABLES LIKE 'join_buffer_size';

-- Set a production-appropriate limit
SET GLOBAL max_connections = 500;
-- Persist across restarts in my.cnf: max_connections = 500

3. wait_timeout und interactive_timeout verstehen und setzen

Der Parameter wait_timeout bestimmt, wie lange MySQL eine nicht-interaktive Verbindung offen hält, bevor sie automatisch geschlossen wird, wenn keine Aktivität stattfindet. Der Standardwert von 28800 Sekunden, also acht Stunden, ist für die meisten Webanwendungen deutlich zu hoch, weil ungenutzte Verbindungen aus abgebrochenen Requests oder fehlerhaftem Connection-Pooling entsprechend lange als aktive Verbindungen im Server verbleiben und Kapazität im Connection-Pooling-Limit blockieren.

Der verwandte Parameter interactive_timeout gilt speziell für Verbindungen, die das CLIENT_INTERACTIVE-Flag setzen, typischerweise interaktive Sessions über den mysql-Client. Für Anwendungsverbindungen ist ausschließlich wait_timeout relevant. Ein sinnvoller Wert für Webanwendungen liegt zwischen 60 und 300 Sekunden, abhängig davon, wie aggressiv das anwendungsseitige Pooling bereits ungenutzte Verbindungen selbst zurückgibt.


-- Check current timeout values
SHOW VARIABLES LIKE 'wait_timeout';
SHOW VARIABLES LIKE 'interactive_timeout';

-- Tighten wait_timeout for non-interactive application connections
SET GLOBAL wait_timeout = 120;

-- Session-level override for a specific connection type
SET SESSION wait_timeout = 60;

4. PHP-FPM-Pool-Größe und MySQL-Verbindungen im Zusammenspiel

Bei PHP-FPM als Anwendungsserver entspricht die Anzahl der PHP-Worker-Prozesse direkt der maximalen Anzahl gleichzeitiger MySQL-Verbindungen dieses einen Servers, sofern jeder Worker eine eigene Verbindung öffnet. Ist pm.max_children in PHP-FPM auf 100 gesetzt und laufen fünf solcher Anwendungsserver, ergibt sich ein theoretischer Spitzenbedarf von 500 gleichzeitigen Verbindungen an MySQL, allein aus einer einzigen Anwendung, ohne Reserve für Batch-Jobs, Monitoring oder administrative Sessions.

Für sauberes Connection-Pooling muss max_connections auf dem MySQL-Server also mindestens die Summe der pm.max_children-Werte aller Anwendungsserver plus einen Sicherheitspuffer abdecken. Wird dieser Zusammenhang übersehen, entstehen Too-many-connections-Fehler exakt dann, wenn die Anwendung unter Lastspitzen am meisten Verbindungen benötigt, was das Problem besonders schmerzhaft macht.


; PHP-FPM pool configuration: www.conf
; pm.max_children directly caps concurrent MySQL connections from this pool
[www]
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20

; Rule of thumb: sum of pm.max_children across all pools + buffer
; must stay below MySQL's max_connections

5. Persistent Connections mit PDO: Chancen und Risiken

PDO unterstützt persistente Verbindungen über die Option PDO::ATTR_PERSISTENT. Eine persistente Verbindung bleibt nach Ende des PHP-Requests im PHP-FPM-Worker-Prozess bestehen und wird bei der nächsten Anfrage desselben Workers wiederverwendet, statt neu aufgebaut zu werden. Das reduziert die Verbindungsaufbauzeit spürbar, insbesondere bei TLS-verschlüsselten Verbindungen, deren Handshake vergleichsweise teuer ist.

Der Nachteil persistenter Verbindungen liegt in ihrem Sitzungszustand: Wurden in einer vorherigen Anfrage temporäre Tabellen erstellt, Sperren gehalten oder Session-Variablen gesetzt, können diese in die nächste Anfrage durchsickern, wenn sie nicht sauber zurückgesetzt werden. Persistente Verbindungen eignen sich daher besonders für Anwendungen mit gleichbleibendem Verbindungsverhalten, während komplexe Anwendungen mit variabler Session-Nutzung eher von einem sauberen externen Connection-Pooling-Mechanismus profitieren.


<?php
declare(strict_types=1);

$dsn = 'mysql:host=db.internal;dbname=shop;charset=utf8mb4';

// Persistent connection: reused across requests within the same worker
$pdo = new PDO($dsn, 'app_user', $password, [
    PDO::ATTR_PERSISTENT => true,
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
]);

// Always reset session state explicitly at request start
// to avoid leaking state from a previous request on this worker
$pdo->exec('SET SESSION sql_mode = "STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION"');

6. Externe Connection Pooler: ProxySQL als Zwischenschicht

Bei sehr vielen Anwendungsservern oder Microservices, die jeweils eigenes anwendungsseitiges Connection-Pooling betreiben, summieren sich die einzelnen Pools schnell zu einer Verbindungsanzahl, die MySQL selbst nicht mehr effizient verwalten kann. ProxySQL setzt als Zwischenschicht zwischen Anwendung und Datenbank an und terminiert selbst tausende Client-Verbindungen, während es intern nur eine deutlich kleinere Anzahl echter Verbindungen zu MySQL unterhält und dynamisch wiederverwendet.

Diese Architektur entkoppelt die Verbindungsanzahl der Anwendungsschicht vollständig von der tatsächlichen Last auf MySQL. Zusätzlich bietet ProxySQL Funktionen wie Query-Routing zwischen Primary und Replicas sowie Connection-Multiplexing, bei dem mehrere Client-Anfragen dieselbe Backend-Verbindung nacheinander nutzen, ohne dass die Anwendung selbst davon etwas mitbekommt. Für Umgebungen mit vielen kleinen, kurzlebigen Anwendungsprozessen ist ProxySQL oft die robustere Lösung gegenüber rein anwendungsseitigem Pooling.


-- ProxySQL admin interface: define backend MySQL server
INSERT INTO mysql_servers (hostgroup_id, hostname, port)
VALUES (0, 'mysql-primary.internal', 3306);

-- Configure connection multiplexing pool size to the backend
UPDATE global_variables
SET variable_value = '200'
WHERE variable_name = 'mysql-max_connections';

LOAD MYSQL SERVERS TO RUNTIME;
LOAD MYSQL VARIABLES TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;

7. Verbindungslecks erkennen mit SHOW PROCESSLIST

Ein Verbindungsleck entsteht, wenn Anwendungscode Verbindungen öffnet, aber nicht zuverlässig wieder freigibt, sodass sich Leerlaufverbindungen im Laufe der Zeit ansammeln, bis das Connection-Pooling-Limit erschöpft ist. SHOW PROCESSLIST und die detailliertere Tabelle information_schema.processlist zeigen alle aktuell offenen Verbindungen mit Status, Leerlaufzeit und der zuletzt ausgeführten Abfrage.

Ein auffälliges Muster für ein Verbindungsleck ist eine große Anzahl Verbindungen im Status Sleep mit langer Zeit seit der letzten Aktivität, oft aus demselben Nutzer und Host stammend. Das deutet darauf hin, dass die Anwendung Verbindungen nicht sauber schließt oder ein Connection Pool mit fehlerhafter Größenkonfiguration ungenutzte Verbindungen nicht rechtzeitig zurückgibt.


-- Find idle connections that may indicate a leak
SELECT id, user, host, db, command, time, state
FROM information_schema.processlist
WHERE command = 'Sleep'
  AND time > 300
ORDER BY time DESC;

-- Count connections grouped by application user
SELECT user, COUNT(*) AS connection_count
FROM information_schema.processlist
GROUP BY user
ORDER BY connection_count DESC;

8. Connection-Pooling in Cloud- und Kubernetes-Umgebungen

In containerisierten Umgebungen mit horizontal skalierenden Pods verschärft sich das Connection-Pooling-Problem zusätzlich, weil die Anzahl der Anwendungsinstanzen dynamisch schwankt. Ein Kubernetes-Deployment, das unter Last von fünf auf fünfzig Pods hochskaliert, vervielfacht die Anzahl potenzieller MySQL-Verbindungen entsprechend, wenn jeder Pod seinen eigenen Verbindungspool unterhält, selbst wenn die tatsächliche Datenbanklast dabei kaum steigt.

Ein zentraler Connection Pooler wie ProxySQL oder ein Managed-Database-Pooling-Dienst des jeweiligen Cloud-Anbieters wird in solchen Umgebungen fast zur Notwendigkeit, weil er als stabiler Zwischenpunkt fungiert, unabhängig von der Skalierung der Anwendungsschicht. Zusätzlich sollten Readiness- und Liveness-Probes in Kubernetes so konfiguriert sein, dass ein Pod beim Beenden seine Datenbankverbindungen sauber schließt, statt sie dem Verbindungslimit als Zombie-Verbindungen zu überlassen.

9. Pooling-Strategien im Überblick

Die folgende Tabelle stellt die gängigen Ansätze für Connection-Pooling gegenüber und ordnet sie nach Einsatzszenario ein.

Strategie Verwaltungsort Skalierbarkeit Ideal bei
Keine Pool-Strategie Pro Request neu verbunden Sehr gering Kleine Skripte, seltene Zugriffe
Persistent Connections (PDO) PHP-FPM-Worker-Prozess Mittel Einzelner Anwendungsserver, gleichbleibende Sessions
ProxySQL als Zwischenschicht Dedizierter Proxy-Layer Sehr hoch Viele Anwendungsserver, Microservices
Managed Cloud Pooler Cloud-Provider-verwaltet Sehr hoch Kubernetes, dynamisch skalierende Umgebungen

Die richtige Strategie hängt maßgeblich von der Anzahl unabhängiger Anwendungsinstanzen ab. Für einen einzelnen Server reichen persistente Verbindungen häufig aus, während verteilte Architekturen mit vielen Instanzen von einem zentralen Connection-Pooling-Layer wie ProxySQL erheblich profitieren.

10. Zusammenfassung

Richtig konfiguriertes Connection-Pooling balanciert drei Ebenen gleichzeitig: den Serverparameter max_connections, der die absolute Obergrenze definiert, die Timeout-Werte wait_timeout, die ungenutzte Verbindungen rechtzeitig freigeben, und die Anwendungskonfiguration, etwa pm.max_children in PHP-FPM, die den tatsächlichen Verbindungsbedarf pro Anwendungsserver bestimmt. Werden diese drei Ebenen nicht aufeinander abgestimmt, entstehen entweder Too-many-connections-Fehler unter Last oder verschwendete Serverressourcen durch überdimensionierte Limits.

Für Umgebungen mit vielen Anwendungsinstanzen, insbesondere in Kubernetes oder Microservice-Architekturen, ersetzt ein zentraler Connection Pooler wie ProxySQL das dezentrale Verbindungsmanagement jeder einzelnen Instanz durch eine gemeinsame, effizient wiederverwendete Verbindungsschicht. Regelmäßiges Monitoring mit SHOW PROCESSLIST deckt Verbindungslecks auf, bevor sie zu Produktionsausfällen führen.

Connection-Pooling richtig konfigurieren, das Wichtigste auf einen Blick

max_connections

Nach verfügbarem RAM dimensionieren, nicht nach Bauchgefühl. Summe aller Anwendungspools plus Puffer abdecken.

wait_timeout

Standardwert von 28800 Sekunden auf 60 bis 300 Sekunden für Webanwendungen senken.

PHP-FPM abstimmen

pm.max_children aller Anwendungsserver zusammen muss unter max_connections bleiben.

Bei Skalierung: ProxySQL

Bei vielen Instanzen oder Kubernetes einen zentralen Connection Pooler als Zwischenschicht einsetzen.

11. FAQ: Connection-Pooling richtig konfigurieren

1Welcher Wert für max_connections?
Nach verfügbarem RAM dimensionieren und die Summe aller pm.max_children-Werte plus Puffer abdecken.
2Was bedeutet Error 1040?
max_connections ist erreicht. Meist ein Zeichen für zu niedriges Limit oder ein Verbindungsleck.
3wait_timeout vs. interactive_timeout?
wait_timeout gilt für Anwendungsverbindungen, interactive_timeout für den mysql-Client mit CLIENT_INTERACTIVE-Flag.
4PHP-FPM und MySQL-Limit?
pm.max_children pro Pool addiert sich über alle Pools und muss unter max_connections bleiben.
5Persistent Connections empfehlenswert?
Ja bei gleichbleibendem Verbindungsverhalten auf einem Server, bei verteilten Systemen eher ein externer Pooler.
6Wofür ProxySQL?
Terminiert viele Client-Verbindungen und hält intern nur wenige echte MySQL-Verbindungen bereit.
7Verbindungsleck erkennen?
Viele Sleep-Verbindungen mit langer Leerlaufzeit vom selben Nutzer in SHOW PROCESSLIST.
8Warum wichtig in Kubernetes?
Dynamische Skalierung lässt die Verbindungsanzahl sprunghaft steigen, ein zentraler Pooler entkoppelt beides.
9Speicherbedarf pro Verbindung?
Typischerweise mehrere Megabyte, abhängig von Session-Puffern wie sort_buffer_size.
10Auch bei geringer Last sinnvoll?
Ja, reduziert Latenz durch entfallenden Verbindungsaufbau, auch bei kleinen Setups mit geringem Aufwand.