MySQL

MySQL Connection Pool Exhausted, Too Many Active Connections

A connection pool reporting itself exhausted, even before hitting MySQL's own hard max_connections ceiling, means your application-level pool has run out of its own configured capacity — which is a separate, usually smaller limit than MySQL's server-side maximum, and needs its own separate diagnosis.

The Problem

The application reports its own connection pool is out of available connections, distinct from a direct MySQL server error:

Error: Pool exhausted. Cannot acquire connection within timeout of 30000ms
sequelize.ConnectionAcquireTimeoutError: Operation timeout

Checking MySQL's own server-side connection count might show it's nowhere near its own max_connections limit, which confirms the bottleneck is specifically in the application's pool configuration or usage, not MySQL's server-level capacity.

Why It Happens

Application-level connection pools have their own configured maximum size, independent of (and usually much smaller than) MySQL's own server-side connection limit. Common causes of hitting this application-level ceiling:

  • The pool's configured maximum size is genuinely too small for actual peak concurrent demand from the application
  • Connections are being checked out from the pool but not consistently released back to it — the same connection-leak pattern that also causes eventual MySQL-server-side exhaustion, just surfacing first at the smaller application-pool level
  • A slow query or a query waiting on a lock holds its connection checked out from the pool for an extended period, effectively reducing the pool's available capacity for the duration of that slow operation
  • Multiple separate application instances or processes each maintain their own independent pool, and the combined total across all instances exceeds what was actually planned for, even if each individual pool's configured size seems reasonable in isolation

The Fix

First, check your application's actual pool configuration against realistic peak concurrent demand:

// Example: Node.js with mysql2
const pool = mysql.createPool({
  connectionLimit: 10, // is this genuinely enough for peak load?
});

Monitor actual pool usage over time (many pool libraries expose metrics like active/idle/waiting connection counts) to understand whether the configured limit is simply too conservative for genuine demand, versus a leak or slow-query issue inflating apparent demand artificially:

console.log(`Active: ${pool.activeConnections()}, Idle: ${pool.idleConnections()}`);

Audit connection release logic for leaks, using the same try/finally or context-manager pattern that prevents this at the source rather than continuing to raise the pool size as a workaround for connections not being properly returned:

const conn = await pool.getConnection();
try {
  // work
} finally {
  conn.release();
}

Identify and optimize slow queries that hold connections checked out for unusually long periods, since a query that takes 10 seconds instead of 100 milliseconds effectively occupies a pool slot 100 times longer than necessary, dramatically reducing the pool's effective throughput for a given configured size:

SHOW FULL PROCESSLIST;

Look for queries with an unusually long Time value relative to what similar queries normally take — these are strong candidates for needing an index, a query rewrite, or investigation into why they're taking longer than expected.

If multiple application instances each maintain independent pools, calculate the realistic combined total across all instances and confirm it stays comfortably under MySQL's own max_connections server-side limit, adjusting either the per-instance pool size or the number of instances if the combined total is unexpectedly close to or exceeding that ceiling:

SHOW VARIABLES LIKE 'max_connections';
-- compare against (number of app instances) x (per-instance pool size)

Still Not Working?

If pool exhaustion happens specifically during traffic spikes rather than under steady, predictable load, consider whether a connection pooler like ProxySQL or PgBouncer's MySQL equivalent, sitting between your application and MySQL, would help absorb burst demand more gracefully than scaling the application-level pool size alone — these tools are specifically designed to multiplex many application-level connection requests over a smaller, more efficiently managed set of actual database connections, which can handle bursty traffic patterns more gracefully than simply making every individual application pool larger.