VillageSQL is a drop-in replacement for MySQL with extensions.
All examples in this guide work on VillageSQL. Install Now →
Why Connections Are Expensive
Each MySQL connection is a thread on the server. The cost at connection time:- TCP/TLS handshake
- Authentication (credential check, privilege resolution)
- Session variable initialization
Application-Level Pooling
Most application frameworks and database drivers include built-in connection pools. These maintain a pool of open connections and lend them to application threads on demand. Node.js (mysql2):- pool size: how many persistent connections to maintain
- max overflow / queue: how many extra connections are allowed under burst load
- recycle / timeout: when to close and reopen connections (prevents stale connections from MySQL’s
wait_timeout)
MySQL’s wait_timeout and Stale Connections
MySQL closes idle connections afterwait_timeout seconds (default: 28800 seconds / 8 hours). This applies to non-interactive connections — the kind application pools use. If your pool holds connections longer than this, they go stale and the next query fails with MySQL server has gone away.
pool_recycle (or the equivalent) to a value shorter than wait_timeout. 3600 seconds (1 hour) is a safe default.
ProxySQL for Multi-Server Setups
Application-level pools work well for a single server. When you have replicas or multiple application servers, a dedicated proxy like ProxySQL sits between the application and MySQL and handles pooling at the infrastructure level. ProxySQL benefits:- Query routing: send reads to replicas, writes to primary
- Connection multiplexing: many application connections share fewer MySQL connections
- Failover: reroute traffic if a server goes down
Tuning max_connections
max_connections sets the server-side ceiling. Each connection consumes memory — a baseline of roughly 1MB, which is the default thread_stack, plus the session’s other static buffers, plus additional memory allocated on demand during query execution (sort buffers, read buffers, tmp tables). Under heavy query load the per-connection cost can reach several MB. Setting max_connections too high wastes memory; too low causes ERROR 1040 (08004): Too many connections.
max_connections = (available RAM for connections) / (memory per connection). Monitor Max_used_connections to understand your actual peak, then set max_connections 20–30% above that.
Frequently Asked Questions
How many connections should my pool have?
Start with the number of CPU cores on the database server, then tune up based on load. A heavily I/O-bound workload can benefit from more connections than CPU cores; a CPU-bound workload can’t. HikariCP’s well-known formulaconnections = (core_count * 2) + effective_spindle_count was designed for spinning disks — it doesn’t apply to SSDs. On modern infrastructure, measure your actual concurrency with Threads_running under peak load and use that as your baseline.
What’s the difference between Threads_connected and Threads_running?
Threads_connected counts all open connections — including idle ones sitting in a pool. Threads_running counts connections actively executing a query right now. The gap between the two is your idle connection count. A large Threads_connected with a small Threads_running is normal for a healthy pool.
Should I use persistent connections in PHP?
Avoid them. PHP has no facility to reset a reused connection — left-open transactions, table locks, temporary tables, and changed session variables carry over to the next request. This can cause deadlocks, data corruption, and hard-to-reproduce bugs. Use a proper connection pool (PDO with pooling, or a proxy like ProxySQL) instead.Troubleshooting
See also
- The MySQL Slow Query Log — distinguish query latency from connection overhead
- MySQL Replication Basics — pooling across a primary and replicas
- Read Replicas in MySQL — pooling per role when reads and writes split
- Using MySQL with ORMs: Django, Rails, and Prisma — the pools every ORM opens

