MySQL Connection Pool Configuration

Complete guide to MySQL connection pool configuration for optimal performance, including server settings, application pool sizing, and troubleshooting connection issues.

MySQL Connection Pool Configuration

Connection pooling is essential for building scalable database applications. Instead of creating a new connection for every request, connection pools maintain a set of reusable connections, dramatically reducing overhead and improving response times.


🎯 Why Connection Pooling Matters

Without PoolingWith Pooling
New connection per requestReuse existing connections
TCP handshake + authentication overheadNear-zero connection overhead
~50-100ms per connection~1ms per connection acquisition
Server overload with connectionsControlled connection count
Resource exhaustion under loadStable performance under load

💡 Key Insight: A single MySQL connection establishment involves TCP handshake, SSL negotiation (if enabled), authentication, and session setup - all of which are eliminated with pooling.


🔧 MySQL Server Connection Settings

Essential Configuration

# /etc/mysql/mysql.conf.d/connections.cnf

[mysqld]
# Maximum simultaneous connections
max_connections = 500

# Reserved connections for SUPER users
max_user_connections = 0  # 0 = unlimited per user (use account limits instead)

# Connection timeout settings
wait_timeout = 28800           # Idle connection timeout (8 hours default)
interactive_timeout = 28800    # Interactive client timeout
connect_timeout = 10           # Connection establishment timeout

# Thread handling
thread_cache_size = 50         # Cache threads for reuse
thread_stack = 256K            # Stack size per thread

# Connection handling (MySQL 8.0+)
# thread_handling = pool-of-threads  # Enterprise feature

Calculating max_connections

-- Rule of thumb formula:
-- max_connections = (Available RAM - OS/Buffer Pool) / per_connection_memory

-- Check current settings
SHOW VARIABLES LIKE 'max_connections';
SHOW VARIABLES LIKE '%timeout%';
SHOW VARIABLES LIKE 'thread%';

-- Check memory per connection
SHOW VARIABLES LIKE 'sort_buffer_size';       -- Per-connection sort buffer
SHOW VARIABLES LIKE 'join_buffer_size';       -- Per-connection join buffer
SHOW VARIABLES LIKE 'read_buffer_size';       -- Per-connection read buffer
SHOW VARIABLES LIKE 'read_rnd_buffer_size';   -- Per-connection random read buffer

-- Estimate per-connection memory (conservative)
-- ~10-20MB per active connection with default settings
-- = (sort_buffer + join_buffer + read_buffer + read_rnd_buffer + thread_stack + net_buffer) * active_queries

Dynamic Connection Limit Adjustment

-- Increase max_connections at runtime (temporary until restart)
SET GLOBAL max_connections = 1000;

-- Check current connection usage
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Max_used_connections';
SHOW STATUS LIKE 'Connections';
SHOW STATUS LIKE 'Aborted_connects';

-- Calculate connection headroom
SELECT
    @@max_connections AS max_conn,
    (SELECT VARIABLE_VALUE FROM performance_schema.global_status
     WHERE VARIABLE_NAME = 'Threads_connected') AS current_conn,
    (SELECT VARIABLE_VALUE FROM performance_schema.global_status
     WHERE VARIABLE_NAME = 'Max_used_connections') AS peak_conn,
    ROUND((SELECT VARIABLE_VALUE FROM performance_schema.global_status
           WHERE VARIABLE_NAME = 'Threads_connected') / @@max_connections * 100, 2) AS usage_pct;

📦 Application-Side Connection Pooling

Java HikariCP (Recommended)

// HikariCP Configuration
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("app_user");
config.setPassword("password");

// Pool sizing
config.setMinimumIdle(10);           // Minimum idle connections
config.setMaximumPoolSize(50);       // Maximum pool size
config.setIdleTimeout(300000);       // 5 minutes idle timeout
config.setMaxLifetime(1800000);      // 30 minutes max connection lifetime
config.setConnectionTimeout(30000);  // 30 seconds connection timeout

// Connection validation
config.setConnectionTestQuery("SELECT 1");
config.setValidationTimeout(5000);   // 5 seconds validation timeout

// MySQL-specific optimizations
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
config.addDataSourceProperty("useServerPrepStmts", "true");
config.addDataSourceProperty("rewriteBatchedStatements", "true");

HikariDataSource ds = new HikariDataSource(config);

Python SQLAlchemy

from sqlalchemy import create_engine
from sqlalchemy.pool import QueuePool

# Create engine with connection pool
engine = create_engine(
    'mysql+pymysql://user:password@localhost/mydb',
    poolclass=QueuePool,
    pool_size=10,           # Number of connections to keep
    max_overflow=20,        # Extra connections when pool exhausted
    pool_timeout=30,        # Seconds to wait for available connection
    pool_recycle=1800,      # Recycle connections after 30 minutes
    pool_pre_ping=True,     # Test connections before use
    echo_pool='debug'       # Log pool activity (development only)
)

# Monitor pool status
from sqlalchemy import event

@event.listens_for(engine, "checkout")
def receive_checkout(dbapi_connection, connection_record, connection_proxy):
    print(f"Connection checked out: {connection_record}")

@event.listens_for(engine, "checkin")
def receive_checkin(dbapi_connection, connection_record):
    print(f"Connection returned: {connection_record}")

Node.js mysql2/promise

const mysql = require('mysql2/promise');

// Create connection pool
const pool = mysql.createPool({
    host: 'localhost',
    user: 'app_user',
    password: 'password',
    database: 'mydb',

    // Pool configuration
    waitForConnections: true,
    connectionLimit: 50,        // Maximum connections
    queueLimit: 0,              // Unlimited queue
    enableKeepAlive: true,
    keepAliveInitialDelay: 30000,

    // Connection options
    connectTimeout: 10000,      // 10 seconds
    acquireTimeout: 60000,      // 60 seconds to acquire
    timeout: 60000,             // Query timeout

    // MySQL settings
    charset: 'utf8mb4',
    timezone: 'Z',
    supportBigNumbers: true,
    bigNumberStrings: true
});

// Use pool
async function query(sql, params) {
    const connection = await pool.getConnection();
    try {
        const [results] = await connection.query(sql, params);
        return results;
    } finally {
        connection.release();
    }
}

// Monitor pool
pool.on('acquire', (connection) => {
    console.log('Connection %d acquired', connection.threadId);
});

pool.on('release', (connection) => {
    console.log('Connection %d released', connection.threadId);
});

pool.on('enqueue', () => {
    console.log('Waiting for available connection slot');
});

PHP PDO with Persistent Connections

<?php
// PHP PDO persistent connections
$dsn = 'mysql:host=localhost;dbname=mydb;charset=utf8mb4';
$options = [
    PDO::ATTR_PERSISTENT => true,  // Enable connection pooling
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES => false,
    PDO::MYSQL_ATTR_FOUND_ROWS => true,
];

try {
    $pdo = new PDO($dsn, 'user', 'password', $options);
} catch (PDOException $e) {
    throw new Exception('Connection failed: ' . $e->getMessage());
}

// For production, use a connection manager class
class ConnectionPool {
    private static $instances = [];

    public static function getConnection(string $name = 'default'): PDO {
        if (!isset(self::$instances[$name])) {
            self::$instances[$name] = self::createConnection();
        }
        return self::$instances[$name];
    }

    private static function createConnection(): PDO {
        // Connection creation logic
    }
}

📊 Pool Sizing Guidelines

Optimal Pool Size Formula

Optimal Pool Size = ((core_count * 2) + effective_spindle_count)

For SSD:
  Pool Size = (CPU cores * 2) + 1

For HDD:
  Pool Size = (CPU cores * 2) + spindle_count

Example (8-core server with SSD):
  Pool Size = (8 * 2) + 1 = 17 connections per application instance

Sizing by Application Type

Application TypePool SizeRationale
Web API (low latency)10-20Short queries, high concurrency
Batch Processing5-10Long transactions, fewer concurrent
Background Jobs2-5Periodic tasks, low concurrency
Real-time Analytics20-50Complex queries, read-heavy
Microservices5-10 per serviceDistributed load

Multi-Instance Calculation

-- Calculate total connections needed
-- Formula: (pool_size * instances) + admin_buffer + replication

-- Example:
-- 4 app servers * 50 connections = 200
-- Admin/monitoring = 10
-- Replication = 5 (if applicable)
-- Buffer (20%) = 43
-- Total max_connections = 258 (round to 300)

-- Verify current distribution
SELECT
    USER,
    HOST,
    COUNT(*) AS connection_count
FROM information_schema.PROCESSLIST
GROUP BY USER, HOST
ORDER BY connection_count DESC;

🔍 Monitoring and Troubleshooting

Connection Monitoring Queries

-- Current connection status
SELECT
    'Current Connections' AS metric,
    (SELECT VARIABLE_VALUE FROM performance_schema.global_status
     WHERE VARIABLE_NAME = 'Threads_connected') AS value
UNION ALL
SELECT
    'Peak Connections',
    (SELECT VARIABLE_VALUE FROM performance_schema.global_status
     WHERE VARIABLE_NAME = 'Max_used_connections')
UNION ALL
SELECT
    'Max Allowed',
    @@max_connections
UNION ALL
SELECT
    'Available',
    @@max_connections - (SELECT VARIABLE_VALUE FROM performance_schema.global_status
                         WHERE VARIABLE_NAME = 'Threads_connected')
UNION ALL
SELECT
    'Usage %',
    ROUND((SELECT VARIABLE_VALUE FROM performance_schema.global_status
           WHERE VARIABLE_NAME = 'Threads_connected') / @@max_connections * 100, 2);

-- Connections by state
SELECT
    COMMAND,
    COUNT(*) AS count,
    GROUP_CONCAT(DISTINCT USER) AS users
FROM information_schema.PROCESSLIST
GROUP BY COMMAND
ORDER BY count DESC;

-- Connections by user/host
SELECT
    USER,
    HOST,
    DB,
    COUNT(*) AS connections,
    SUM(CASE WHEN COMMAND = 'Sleep' THEN 1 ELSE 0 END) AS sleeping,
    SUM(CASE WHEN COMMAND != 'Sleep' THEN 1 ELSE 0 END) AS active
FROM information_schema.PROCESSLIST
GROUP BY USER, HOST, DB
ORDER BY connections DESC;

-- Long-running connections
SELECT
    ID,
    USER,
    HOST,
    DB,
    COMMAND,
    TIME AS seconds,
    STATE,
    LEFT(INFO, 100) AS query
FROM information_schema.PROCESSLIST
WHERE TIME > 300  -- Connections older than 5 minutes
ORDER BY TIME DESC;

Connection Issues and Solutions

-- Issue: "Too many connections" error
-- Check current vs max
SELECT @@max_connections,
       (SELECT VARIABLE_VALUE FROM performance_schema.global_status
        WHERE VARIABLE_NAME = 'Threads_connected') AS current;

-- Solution 1: Increase max_connections (temporary)
SET GLOBAL max_connections = 500;

-- Solution 2: Kill idle connections
SELECT CONCAT('KILL ', ID, ';')
FROM information_schema.PROCESSLIST
WHERE COMMAND = 'Sleep'
    AND TIME > 3600  -- Idle > 1 hour
    AND USER != 'system user';

-- Solution 3: Reduce application pool size

-- Issue: Connection refused
-- Check for connection errors
SHOW STATUS LIKE 'Aborted_connects';
SHOW STATUS LIKE 'Connection_errors%';

-- Issue: Connections timing out
-- Adjust wait_timeout for your workload
SET GLOBAL wait_timeout = 300;  -- 5 minutes for web apps
SET GLOBAL interactive_timeout = 28800;  -- 8 hours for admin tools

Automated Monitoring Script

-- Create monitoring procedure
DELIMITER //

CREATE PROCEDURE sp_monitor_connections()
BEGIN
    DECLARE v_current INT;
    DECLARE v_max INT;
    DECLARE v_threshold DECIMAL(5,2) DEFAULT 80.00;

    SELECT @@max_connections INTO v_max;

    SELECT VARIABLE_VALUE INTO v_current
    FROM performance_schema.global_status
    WHERE VARIABLE_NAME = 'Threads_connected';

    IF (v_current / v_max * 100) > v_threshold THEN
        -- Log alert
        INSERT INTO connection_alerts (
            alert_time,
            current_connections,
            max_connections,
            usage_percent,
            alert_type
        ) VALUES (
            NOW(),
            v_current,
            v_max,
            ROUND(v_current / v_max * 100, 2),
            'HIGH_CONNECTION_USAGE'
        );

        -- Optionally kill old idle connections
        -- Be careful with this in production!
    END IF;

    -- Return current status
    SELECT
        v_current AS current_connections,
        v_max AS max_connections,
        ROUND(v_current / v_max * 100, 2) AS usage_percent,
        CASE
            WHEN (v_current / v_max * 100) > 90 THEN 'CRITICAL'
            WHEN (v_current / v_max * 100) > 80 THEN 'WARNING'
            ELSE 'OK'
        END AS status;
END //

DELIMITER ;

-- Schedule monitoring
CREATE EVENT evt_connection_monitor
    ON SCHEDULE EVERY 1 MINUTE
    ON COMPLETION PRESERVE
DO
    CALL sp_monitor_connections();

🚀 Advanced Configuration

Per-User Connection Limits

-- Limit connections per user
ALTER USER 'app_user'@'%'
    WITH MAX_USER_CONNECTIONS 100;

-- Limit queries per hour
ALTER USER 'reporting_user'@'%'
    WITH MAX_QUERIES_PER_HOUR 10000
         MAX_UPDATES_PER_HOUR 100
         MAX_USER_CONNECTIONS 10;

-- View user resource limits
SELECT
    USER,
    HOST,
    MAX_USER_CONNECTIONS,
    MAX_QUESTIONS,
    MAX_UPDATES,
    MAX_CONNECTIONS
FROM mysql.user
WHERE MAX_USER_CONNECTIONS > 0 OR MAX_QUESTIONS > 0;

Connection Pooling with ProxySQL

-- ProxySQL provides server-side connection pooling
-- Connect to ProxySQL admin interface

-- Configure backend servers
INSERT INTO mysql_servers (hostgroup_id, hostname, port, max_connections)
VALUES (10, '192.168.1.101', 3306, 100);

-- Configure connection multiplexing
UPDATE global_variables
SET variable_value = 'true'
WHERE variable_name = 'mysql-multiplexing';

UPDATE global_variables
SET variable_value = '1000'
WHERE variable_name = 'mysql-max_connections';

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

✅ Best Practices Checklist

Server Configuration

  • [ ] Set max_connections based on available memory
  • [ ] Configure appropriate timeout values
  • [ ] Enable thread caching
  • [ ] Monitor connection usage regularly
  • [ ] Set per-user connection limits

Application Configuration

  • [ ] Use connection pooling in all applications
  • [ ] Size pools based on workload characteristics
  • [ ] Enable connection validation/health checks
  • [ ] Configure connection lifetime/recycling
  • [ ] Handle connection errors gracefully

Operations

  • [ ] Monitor connection metrics continuously
  • [ ] Alert on high connection usage
  • [ ] Review and optimize pool sizes periodically
  • [ ] Document connection requirements per application
  • [ ] Test failover with connection pooling

🎓 Conclusion

Proper connection pool configuration is crucial for building scalable MySQL applications. By understanding both server-side settings and application-side pooling, you can optimize resource usage and ensure consistent performance under varying loads.

💡 Pro Tip: Start with conservative pool sizes and increase based on monitoring data. Over-provisioning pools wastes server resources, while under-provisioning causes connection wait timeouts.


About the Author

Jamaurice Holt is a Cloud Database Expert and AWS Solutions Architect with over 10 years of experience in database optimization, high availability, and cloud migrations. Specializing in MySQL, SQL optimization, and enterprise database solutions.

Related Articles

Need help with MySQL connection pooling? Contact us for expert database consulting.

Continue Reading

Leadership

Leadership in Motion: Technical Leadership Lessons from Database Operations

How a decade of database administration shaped my approach to technical leadership — lessons on incident response, mentoring engineers,...

July 10, 2024 · Read article →
Performance

Database Performance Tuning: Lessons from the Trenches

Real-world strategies for optimizing database performance in high-traffic production environments — query plan analysis, indexing, caching,...

August 22, 2024 · Read article →
AI

AI Governance and Hardware: Why Sovereign Refusal Belongs in Silicon

Software guardrails can be prompted around. The real frontier in AI safety is anchoring refusal below the model — at the hardware level. A...

September 15, 2024 · Read article →