MySQL Connection Pooling with ProxySQL for WordPress

ProxySQL is a high-performance MySQL proxy that sits between your application and MySQL server, providing connection pooling, query routing, read/write splitting, and query caching. For WordPress sites on high-traffic servers, ProxySQL reduces the overhead of establishing 100+ short-lived database connections per page load.

Problem: A WordPress site's PHP-FPM workers each open a new database connection per request — under high concurrency, MySQL hits its max_connections limit and returns "Too many connections" errors, even when queries themselves are fast.

Solution: Deploy ProxySQL as a connection pooler between PHP-FPM and MySQL. ProxySQL maintains a persistent pool of MySQL connections and multiplexes hundreds of PHP-FPM connections onto a smaller number of backend connections. Update wp-config.php to point DB_HOST at the ProxySQL port (6033 by default) — no other WordPress changes are needed.

The examples below install and configure ProxySQL for a single WordPress server, set up connection pooling with appropriate limits, enable query mirroring to a read replica, and configure query rules to route SELECT queries to a replica.

# Install ProxySQL (Ubuntu/Debian)
wget https://github.com/sysown/proxysql/releases/download/v2.6.3/proxysql_2.6.3-debian12_amd64.deb
dpkg -i proxysql_2.6.3-debian12_amd64.deb
systemctl enable proxysql && systemctl start proxysql

# ProxySQL admin interface runs on port 6032 (not 3306)
# Application connects to ProxySQL on port 6033

# Connect to ProxySQL admin
mysql -u admin -padmin -h 127.0.0.1 -P 6032

# Configure the MySQL backend servers
# hostgroup_id 10 = writer, hostgroup_id 20 = reader
INSERT INTO mysql_servers (hostgroup_id, hostname, port, max_connections, comment)
VALUES
    (10, '127.0.0.1', 3306, 100, 'primary-writer'),
    (20, '127.0.0.1', 3306, 200, 'primary-reader'),
    (20, '192.168.1.11', 3306, 200, 'replica-reader');

# Configure the MySQL user (must exist in MySQL too)
INSERT INTO mysql_users (username, password, default_hostgroup, max_connections)
VALUES ('wp_user', 'wp_password', 10, 200);

# Apply and save the configuration
LOAD MYSQL SERVERS TO RUNTIME;
LOAD MYSQL USERS TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;
SAVE MYSQL USERS TO DISK;

Configure query routing rules and connection pool settings:

-- Connect to ProxySQL admin (port 6032)

-- Route SELECT queries to the reader hostgroup (read/write splitting)
INSERT INTO mysql_query_rules (rule_id, active, match_digest, destination_hostgroup, apply)
VALUES
    (1, 1, '^SELECT.*FOR UPDATE$', 10, 1),  -- SELECT FOR UPDATE -> writer
    (2, 1, '^SELECT',              20, 1);  -- all other SELECTs -> reader

-- Route WordPress admin (wp-admin) queries to the writer to avoid replication lag issues
INSERT INTO mysql_query_rules (rule_id, active, match_digest, proxy_addr, destination_hostgroup, apply)
VALUES (3, 1, '.*', '127.0.0.1', 10, 0);
-- Note: better approach — use a separate wp-config.php DB_HOST for admin

-- Connection pooling settings (mysql_connection_pool_variables table)
UPDATE global_variables SET variable_value = 200
    WHERE variable_name = 'mysql-max_connections';

UPDATE global_variables SET variable_value = 10000
    WHERE variable_name = 'mysql-connection_max_age_ms';  -- recycle old connections

UPDATE global_variables SET variable_value = 4
    WHERE variable_name = 'mysql-connection_warming';

-- Apply all query rules
LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;
LOAD MYSQL VARIABLES TO RUNTIME;
SAVE MYSQL VARIABLES TO DISK;

-- Monitor connection pool stats
SELECT hostgroup, srv_host, status, connused, connfree, queries
FROM stats_mysql_connection_pool
ORDER BY hostgroup, srv_host;

-- Monitor query routing
SELECT digest_text, count_star, sum_time, hostgroup
FROM stats_mysql_query_digest
ORDER BY sum_time DESC
LIMIT 10;

NOTE: Update wp-config.php to connect through ProxySQL: set DB_HOST to 127.0.0.1 and DB_PORT to 6033. For WordPress multisite or high-traffic e-commerce, ProxySQL's connection multiplexing reduces MySQL's thread count from 200+ down to 20-30 persistent backend connections, dramatically reducing the overhead of connection setup on each request.

Leave Comment

Your email address will not be published. Required fields are marked *