DEV Community

Cover image for MySQL Connection Pooling Explained: Do 50 Concurrent Users Need 50 Database Connections?
Kader Khan
Kader Khan

Posted on

MySQL Connection Pooling Explained: Do 50 Concurrent Users Need 50 Database Connections?

When configuring a database connection in a backend application, you've probably seen code like this:

sqlDB.SetMaxOpenConns(5)
sqlDB.SetMaxIdleConns(1)
sqlDB.SetConnMaxIdleTime(2 * time.Minute)
sqlDB.SetConnMaxLifetime(30 * time.Minute)
Enter fullscreen mode Exit fullscreen mode

Most developers understand that these settings are related to database connections.

But the bigger questions are:

  • What does MaxOpenConns actually control?
  • What is the difference between MaxIdleConns and ConnMaxIdleTime?
  • Does having 50 concurrent users mean you need 50 MySQL connections?
  • How many database connections should your application actually have?
  • What happens when your connection pool becomes full?

The short answer to one of the most common questions is:

50 concurrent users do not mean you need 50 MySQL connections.

Understanding this distinction is important when building scalable backend applications—especially for SaaS and multi-tenant systems.

Let's break it down.


What Is a MySQL Database Connection?

A database connection is simply a communication channel between your application and the MySQL server.

For example, when a user opens their dashboard:

User
  ↓
Frontend
  ↓
Backend API
  ↓
MySQL Connection
  ↓
MySQL Database
Enter fullscreen mode Exit fullscreen mode

Your backend might execute a query like:

SELECT * FROM users WHERE id = 10;
Enter fullscreen mode Exit fullscreen mode

The backend sends the query through a database connection, MySQL processes it, and the result is returned to the application.

A simple request might look like this:

Request arrives
      ↓
Backend needs data
      ↓
Gets a database connection
      ↓
Executes query
      ↓
Receives result
      ↓
Returns connection to the pool
Enter fullscreen mode Exit fullscreen mode

That last step—returning the connection to the pool—is where connection pooling becomes important.


Why Not Create a New MySQL Connection for Every Request?

Technically, you could create a new database connection for every request:

Request 1 → Create Connection → Run Query → Close

Request 2 → Create Connection → Run Query → Close

Request 3 → Create Connection → Run Query → Close
Enter fullscreen mode Exit fullscreen mode

But this is inefficient.

Creating a database connection can involve:

  • Network communication
  • Authentication
  • Server-side resource allocation
  • Session initialization
  • Additional CPU and memory usage

If your application receives hundreds or thousands of requests, constantly creating and destroying connections creates unnecessary overhead.

Instead, backend applications use a database connection pool.


What Is a Database Connection Pool?

A connection pool is a collection of reusable database connections.

Instead of creating a new connection for every request, your application can borrow an available connection from the pool.

                Backend Application
                         │
                         ▼
                  Connection Pool
              ┌────────┬────────┬────────┐
              │   C1    │   C2     │   C3    │
              └────────┴────────┴────────┘
                         │
                         ▼
                       MySQL
Enter fullscreen mode Exit fullscreen mode

The lifecycle looks like this:

Request
   ↓
Borrow Connection
   ↓
Execute Query
   ↓
Query Finished
   ↓
Return Connection to Pool
Enter fullscreen mode Exit fullscreen mode

Then another request can reuse the same connection:

Request A → Uses C1 → Query Finished → C1 returns to pool

Request B → Uses C1
Enter fullscreen mode Exit fullscreen mode

This is much more efficient than constantly creating and closing database connections.


Understanding MaxOpenConns in Go

Let's start with the most important setting:

sqlDB.SetMaxOpenConns(5)
Enter fullscreen mode Exit fullscreen mode

This means:

Your application can have a maximum of 5 open database connections at the same time.

However, there is an important detail:

MaxOpenConns = 5 does not mean Go immediately creates 5 connections when your application starts.

Connections are generally created as needed.

For example:

Application starts
       ↓
No database activity
       ↓
0 connections may exist
Enter fullscreen mode Exit fullscreen mode

Then traffic arrives:

Request 1 → Connection C1 created

Request 2 → Connection C2 created

Request 3 → Connection C3 created
Enter fullscreen mode Exit fullscreen mode

The pool can continue opening connections until it reaches:

Maximum Open Connections = 5
Enter fullscreen mode Exit fullscreen mode

Once all 5 connections are busy, new requests that need a database connection may have to wait.


Do 50 Concurrent Users Need 50 MySQL Connections?

No.

This is one of the most common misunderstandings when designing backend infrastructure.

The key concept is:

Concurrent users are not the same as concurrent database queries.

Imagine your application has:

50 users online
Enter fullscreen mode Exit fullscreen mode

Those users may be doing completely different things:

  • Reading a page
  • Looking at previously loaded data
  • Typing into a form
  • Waiting before clicking something
  • Calling an API that doesn't require the database
  • Running a database query

At a specific moment, perhaps only 5 requests actually need MySQL.

Your system might look like this:

50 Concurrent Users
        │
        ▼
 Backend Application
        │
        ▼
   Connection Pool
        │
        ├── C1 → Running Query
        ├── C2 → Running Query
        ├── C3 → Running Query
        ├── C4 → Running Query
        └── C5 → Running Query
Enter fullscreen mode Exit fullscreen mode

The remaining users do not automatically need their own database connection.


Practical Example: 50 Users With Only 5 Database Connections

Let's say you have:

50 concurrent users
5 database connections
Enter fullscreen mode Exit fullscreen mode

Each database query takes approximately:

50 milliseconds
Enter fullscreen mode Exit fullscreen mode

At the first moment:

0ms

C1 → Request 1
C2 → Request 2
C3 → Request 3
C4 → Request 4
C5 → Request 5
Enter fullscreen mode Exit fullscreen mode

After approximately 50 milliseconds, those queries finish.

Now the same connections can serve new requests:

50ms later

C1 → Request 6
C2 → Request 7
C3 → Request 8
C4 → Request 9
C5 → Request 10
Enter fullscreen mode Exit fullscreen mode

The connections are continuously reused.

This is why:

A small connection pool can support many users when queries are fast and connections are released quickly.

The important factor isn't simply how many users exist.

It's how many requests need database access at the same time and how long those database operations take.


What Happens When All Database Connections Are Busy?

Now let's change the scenario.

You still have:

MaxOpenConns = 5
Enter fullscreen mode Exit fullscreen mode

But instead of taking 50 milliseconds, each query takes:

2 seconds
Enter fullscreen mode Exit fullscreen mode

Now the situation looks like this:

5 Connections
      ↓
5 Long-Running Queries
      ↓
All Connections Busy
      ↓
New Database Requests Wait
Enter fullscreen mode Exit fullscreen mode

If many requests arrive simultaneously:

50 Requests
    │
    ├── 5 → Running queries
    │
    └── 45 → Waiting for a connection
Enter fullscreen mode Exit fullscreen mode

This is called connection pool contention.

Your application isn't necessarily slow because it has too few users or too little CPU.

The problem may simply be that database connections are occupied for too long.


Connection Pool Size Depends on Workload, Not User Count

This is the most important takeaway:

Don't calculate database connections based only on the number of concurrent users.

Instead, consider:

  • How many requests need database access?
  • How many database queries run simultaneously?
  • How long does each query take?
  • How powerful is the database server?
  • How much CPU and memory are available?
  • How many application instances are running?

For example:

100 Users
×
Fast Queries
=
Maybe a Small Pool Is Enough
Enter fullscreen mode Exit fullscreen mode

But:

20 Users
×
Slow Queries
=
You May Still Have Connection Contention
Enter fullscreen mode Exit fullscreen mode

The workload matters more than the user count.


What Does MaxIdleConns Mean?

Now let's look at:

sqlDB.SetMaxIdleConns(1)
Enter fullscreen mode Exit fullscreen mode

This controls how many unused connections the pool can keep available.

In simple terms:

Keep up to 1 idle connection ready for future requests.

Imagine your application temporarily opens 3 connections:

C1 → Running Query
C2 → Running Query
C3 → Running Query
Enter fullscreen mode Exit fullscreen mode

Eventually, all queries finish:

C1 → Idle
C2 → Idle
C3 → Idle
Enter fullscreen mode Exit fullscreen mode

But you configured:

sqlDB.SetMaxIdleConns(1)
Enter fullscreen mode Exit fullscreen mode

Conceptually, the pool will only retain up to one idle connection for reuse, while excess idle connections can be closed.

C1 → Keep Available

C2 → May Be Closed

C3 → May Be Closed
Enter fullscreen mode Exit fullscreen mode

This can be useful for low-traffic applications where keeping many unused database connections open provides little benefit.


ConnMaxIdleTime: How Long Can a Connection Stay Unused?

Consider:

sqlDB.SetConnMaxIdleTime(2 * time.Minute)
Enter fullscreen mode Exit fullscreen mode

This means:

A connection that remains idle for approximately 2 minutes becomes eligible to be closed.

Example:

10:00 → Connection created

10:01 → Query executed

10:01 → Connection becomes idle

10:02 → Still idle

10:03 → Idle for approximately 2 minutes
        ↓
Eligible to be closed
Enter fullscreen mode Exit fullscreen mode

The important detail is:

The idle timer is based on when the connection was last used—not when it was created.

So if a connection is frequently reused, it won't become idle for long enough to be removed.


ConnMaxLifetime: How Long Can a Connection Live?

Now consider:

sqlDB.SetConnMaxLifetime(30 * time.Minute)
Enter fullscreen mode Exit fullscreen mode

This controls the total age of a connection.

In simple terms:

A connection should not live forever. After approximately 30 minutes, it becomes eligible for replacement.

Example:

10:00 → Connection created

10:05 → Query

10:10 → Query

10:15 → Query

10:20 → Query

10:25 → Query

10:30 → Connection is approximately 30 minutes old
        ↓
Eligible for replacement
Enter fullscreen mode Exit fullscreen mode

Even if the connection has been actively used, its total lifetime has reached the configured limit.

Connection lifetime limits can be useful in environments involving:

  • Database failovers
  • Network infrastructure changes
  • Load balancers
  • Long-running applications
  • Server-side connection policies

ConnMaxIdleTime vs ConnMaxLifetime

These two settings are easy to confuse.

Here's the simplest way to remember them.

ConnMaxIdleTime

Ask:

How long can a connection sit unused?

Last Query
    ↓
Becomes Idle
    ↓
Idle Too Long
    ↓
Eligible to Close
Enter fullscreen mode Exit fullscreen mode

ConnMaxLifetime

Ask:

How old can the connection become?

Connection Created
       ↓
Time Passes
       ↓
Maximum Lifetime Reached
       ↓
Eligible for Replacement
Enter fullscreen mode Exit fullscreen mode

So:

ConnMaxIdleTime
→ Limits unused time

ConnMaxLifetime
→ Limits total connection age
Enter fullscreen mode Exit fullscreen mode

The Restaurant Analogy: Understanding Connection Pools Easily

A simple way to understand connection pooling is to think about a restaurant.

Imagine the restaurant has:

50 customers
Enter fullscreen mode Exit fullscreen mode

Does it need:

50 tables?
Enter fullscreen mode Exit fullscreen mode

Not necessarily.

If customers finish eating and leave, the same tables can serve new customers.

Customer 1 → Uses Table 1 → Leaves

Customer 2 → Uses Table 1
Enter fullscreen mode Exit fullscreen mode

Database connections work similarly:

Request 1 → Uses Connection C1 → Finishes

Request 2 → Uses Connection C1
Enter fullscreen mode Exit fullscreen mode

Now imagine every customer stays at their table for two hours.

With only 5 tables:

5 Tables
    ↓
All Occupied
    ↓
Everyone Else Waits
Enter fullscreen mode Exit fullscreen mode

This is similar to slow database queries.

When queries take too long, connections remain occupied, and new requests must wait.


How Much Memory Does a MySQL Connection Use?

Another common question is:

How much RAM does one MySQL connection consume?

There isn't a single fixed answer.

A MySQL connection does not always consume exactly:

1 MB
2 MB
5 MB
Enter fullscreen mode Exit fullscreen mode

Memory usage depends on several factors, including:

  • MySQL configuration
  • Session settings
  • Query complexity
  • Sorting operations
  • JOIN operations
  • Temporary tables
  • Per-session buffers
  • Prepared statements
  • Concurrent workload

So this calculation is often misleading:

5 Connections × 2 MB = 10 MB
Enter fullscreen mode Exit fullscreen mode

MySQL memory usage is more complex.

A simplified view looks like this:

Database Server Memory
│
├── MySQL Global Memory
├── InnoDB Buffer Pool
├── Connection / Session Memory
├── Query Buffers
├── Temporary Memory
└── Operating System Overhead
Enter fullscreen mode Exit fullscreen mode

This is why allowing thousands of connections on a small database instance can create serious resource pressure.

More connections do not automatically mean better performance.


Connection Pooling in Multi-Tenant SaaS Applications

Connection pooling becomes especially important in multi-tenant SaaS architecture.

Imagine you have:

200 tenants
Enter fullscreen mode Exit fullscreen mode

And every tenant can create up to:

5 database connections
Enter fullscreen mode Exit fullscreen mode

The theoretical maximum becomes:

200 × 5 = 1000 connections
Enter fullscreen mode Exit fullscreen mode

This does not mean all 1,000 connections will always be open.

But if many tenants become active simultaneously, the total number of connections can increase significantly.

For example:

Tenant A → 5 connections

Tenant B → 5 connections

Tenant C → 4 connections

Tenant D → 5 connections

Tenant E → 3 connections

...
Enter fullscreen mode Exit fullscreen mode

Across hundreds of tenants, a seemingly small pool size can become a large system-wide database load.

So the question shouldn't only be:

Is 5 connections enough for one tenant?

You should also ask:

What happens when 200 tenants become active at the same time?

This is where database capacity planning becomes important.


A Practical Connection Pool Configuration for Low-Traffic Applications

Suppose you have a relatively low-traffic application or tenant.

You might start with:

sqlDB.SetMaxOpenConns(5)
sqlDB.SetMaxIdleConns(1)
sqlDB.SetConnMaxIdleTime(2 * time.Minute)
sqlDB.SetConnMaxLifetime(30 * time.Minute)
Enter fullscreen mode Exit fullscreen mode

Conceptually:

MaxOpenConns = 5
→ Maximum number of open connections

MaxIdleConns = 1
→ Keep up to 1 unused connection ready

ConnMaxIdleTime = 2 minutes
→ Remove connections that stay unused for too long

ConnMaxLifetime = 30 minutes
→ Periodically recycle older connections
Enter fullscreen mode Exit fullscreen mode

However, these values are not universal magic numbers.

They may be appropriate for one application and completely wrong for another.

The correct configuration depends on real production metrics.


How to Tune Your Database Connection Pool

One of the biggest mistakes is assuming:

More database connections = better performance

That's not always true.

For example:

MaxOpenConns = 5
Enter fullscreen mode Exit fullscreen mode

could perform better than:

MaxOpenConns = 100
Enter fullscreen mode Exit fullscreen mode

if your database cannot efficiently process 100 concurrent queries.

Instead of guessing, monitor your system.

Important metrics include:

  • Active database connections
  • Open and idle connections
  • Connection wait count
  • Connection wait duration
  • Query latency
  • Slow queries
  • Database CPU usage
  • Database memory usage
  • Application response time

In Go, you can inspect your connection pool using:

stats := sqlDB.Stats()

fmt.Println("Open Connections:", stats.OpenConnections)
fmt.Println("In Use:", stats.InUse)
fmt.Println("Idle:", stats.Idle)
fmt.Println("Wait Count:", stats.WaitCount)
fmt.Println("Wait Duration:", stats.WaitDuration)
Enter fullscreen mode Exit fullscreen mode

If WaitCount continues increasing, it may indicate that requests are frequently waiting for an available connection.

At that point, you might consider increasing MaxOpenConns.

But don't increase it blindly.


When Increasing MaxOpenConns Makes Things Worse

Imagine your database already has:

High CPU Usage
High Memory Usage
Slow Queries
Enter fullscreen mode Exit fullscreen mode

Increasing your connection pool from:

5 → 50
Enter fullscreen mode Exit fullscreen mode

may allow more queries to hit the database simultaneously.

That can make the situation even worse.

Sometimes the real problem looks like this:

Slow Query
     ↓
Missing Index
     ↓
Query Takes Longer
     ↓
Connection Stays Busy
     ↓
Requests Start Waiting
Enter fullscreen mode Exit fullscreen mode

In that situation, the best solution might be:

Optimize the query and add proper indexes.

Not:

Keep increasing the connection pool size.

A connection pool can manage database access efficiently, but it cannot fix poorly performing queries.


A Simple Mental Model for Database Connections

Think about your system like this:

Users
  ↓
HTTP Requests
  ↓
Backend Application
  ↓
Connection Pool
  ↓
MySQL Database
Enter fullscreen mode Exit fullscreen mode

The number of:

Users
Enter fullscreen mode Exit fullscreen mode

does not directly determine the number of:

Database Connections
Enter fullscreen mode Exit fullscreen mode

What matters more is:

Concurrent Database Work
        ×
How Long Each Query Holds a Connection
Enter fullscreen mode Exit fullscreen mode

Fast queries release connections quickly.

Slow queries keep connections busy.

That's the fundamental relationship.


Final Thoughts: How Many MySQL Connections Do You Really Need?

A database connection pool is essentially a resource management system.

You don't need one database connection for every user.

Instead, a relatively small number of connections can be shared across many users and requests.

The four key settings are:

MaxOpenConns
→ Maximum number of open database connections

MaxIdleConns
→ Maximum number of unused connections kept ready

ConnMaxIdleTime
→ How long an unused connection can remain idle

ConnMaxLifetime
→ How old a connection can become before being recycled
Enter fullscreen mode Exit fullscreen mode

The most important lesson is:

Never determine your database connection pool size simply by looking at the number of concurrent users.

Instead, consider:

  • Concurrent database workload
  • Query execution time
  • Connection wait time
  • Database CPU capacity
  • Available memory
  • Application architecture
  • Number of application instances
  • Total system-wide database connections

This becomes especially important in multi-tenant SaaS systems, where a small connection pool per tenant can eventually create hundreds or thousands of connections across the entire infrastructure.

Redis helps by reducing how often your application needs to go to MySQL at all.

The mechanism: Cache-Aside Pattern

Without Redis:

User Request
     ↓
Backend
     ↓
MySQL Connection Pool
     ↓
MySQL Query
     ↓
Return Data
Enter fullscreen mode Exit fullscreen mode

Every request that needs the same data may hit MySQL.

With Redis:

User Request
     ↓
Backend
     ↓
Check Redis
     │
     ├── Cache HIT  → Return data immediately
     │
     └── Cache MISS → Query MySQL
                        ↓
                     Save in Redis
                        ↓
                     Return data
Enter fullscreen mode Exit fullscreen mode

Practical Example

Imagine 50 users open the same dashboard.

The dashboard needs this data:

SELECT * FROM company_settings WHERE tenant_id = 101;
Enter fullscreen mode Exit fullscreen mode

Without Redis

Potentially:

50 Users
    ↓
50 Requests
    ↓
MySQL
    ↓
Same query executed many times
Enter fullscreen mode Exit fullscreen mode

Your MySQL connection pool might become busy:

5 Connections
    ↓
5 Queries Running
    ↓
Other Requests Wait
Enter fullscreen mode Exit fullscreen mode

With Redis

The first request:

User 1
   ↓
Redis: Data not found ❌
   ↓
MySQL Query
   ↓
Get Data
   ↓
Save Data in Redis
Enter fullscreen mode Exit fullscreen mode

Now Redis contains:

Key:
tenant:101:settings

Value:
{
  "theme": "dark",
  "company_name": "ABC Ltd"
}
Enter fullscreen mode Exit fullscreen mode

The next 49 users:

User 2  → Redis HIT ✅
User 3  → Redis HIT ✅
User 4  → Redis HIT ✅
...
User 50 → Redis HIT ✅
Enter fullscreen mode Exit fullscreen mode

Now the flow becomes:

50 Users
    ↓
Backend
    ↓
Redis
    ↓
Most requests never reach MySQL
Enter fullscreen mode Exit fullscreen mode

How Redis Helps Your Connection Pool

Redis does not directly increase your MySQL connection limit.

Instead, it reduces the pressure on MySQL.

Without Redis:

50 Requests
    ↓
50 Requests need MySQL
    ↓
Only 5 connections available
    ↓
Requests wait
Enter fullscreen mode Exit fullscreen mode

With Redis:

50 Requests
    ↓
45 Cache HITs → Redis
5 Cache MISSes → MySQL
Enter fullscreen mode Exit fullscreen mode

Now only a small number of requests need MySQL connections.

So your existing configuration:

sqlDB.SetMaxOpenConns(5)
Enter fullscreen mode Exit fullscreen mode

can potentially handle much more traffic because many requests no longer need a database connection.


The Simple Relationship

Think of it like this:

Without Redis:

Users
  ↓
Backend
  ↓
MySQL Connection Pool
  ↓
MySQL
Enter fullscreen mode Exit fullscreen mode

With Redis:

Users
  ↓
Backend
  ↓
Redis Cache
  │
  ├── HIT  → Return Data ⚡
  │
  └── MISS → MySQL Connection Pool
                  ↓
                MySQL
Enter fullscreen mode Exit fullscreen mode

So the key idea is:

Connection pooling manages how your application accesses MySQL. Redis reduces how often your application needs to access MySQL.

They solve different problems, but together they can significantly improve scalability.

One-line summary:

MySQL Connection Pool = manages database connections efficiently. Redis = prevents unnecessary database queries.

For your multi-tenant SaaS example, Redis can be especially useful for frequently requested but rarely changing data such as tenant settings, user permissions, dashboard summaries, configuration data, and frequently accessed lookup data.

The goal is not to create as many database connections as possible.

The goal is simple:

Use enough connections to efficiently handle your workload—without overwhelming your database.


Key Takeaway

If you remember only one thing from this article, remember this:

Concurrent users ≠ concurrent database connections.

A well-configured connection pool allows a small number of database connections to efficiently serve a much larger number of users—provided your queries are fast and your database is properly monitored.

Top comments (0)