Home › Database › MySQL Too Many Connections — Pool and Prune Sleepers
Intermediate 5 min · September 23, 2026

MySQL Too Many Connections — Pool and Prune Sleepers

Fix MySQL error 1040 by sizing app pools below max_connections, pruning sleeping threads, and tuning wait_timeout.

N
Naren Founder & Principal Engineer

20+ years shipping high-throughput database systems. Lessons pulled from things that broke in production.

Follow
✓ Production
production tested
September 24, 2026
last updated
1,942
articles · all by Naren
Before you start⏱ 12 min
  • ✓A MySQL server where you can run SHOW PROCESSLIST
  • ✓An app with a connection pool (Hikari, SQLAlchemy, or similar)
  • ✓Admin credentials with SUPER or CONNECTION_ADMIN privilege
 ● Production Incident 🔎 Debug Guide
⚡Quick Answer
  • Error 1040 means every connection slot is taken — compare max_connections against the sum of all app pool maximums, since oversized pools usually oversubscribe the server
  • Run SHOW PROCESSLIST and look for idle Sleep rows: they're leaked or oversized pool connections holding slots while doing nothing, and they're safe to prune
  • Lower wait_timeout so abandoned threads die in minutes, and fix the app to close what it opens instead of relying on the server to clean up
  • Keep a SUPER-privileged admin account free so you can always get in during a full outage — then shrink pools like Hikari maximumPoolSize to fit the budget
✦ Definition~90s read
What is MySQL Too Many Connections Fix?

Every MySQL connection costs a thread plus session buffers, so max_connections exists to cap memory, not to annoy you. The default 151 fits small servers; production boxes often run 300–500, but each slot consumes RAM and context switches — setting 2000 'to be safe' trades connection errors for OOM kills.

★
Think of MySQL as a parking lot with 151 spaces.

The server also reserves one extra slot beyond the limit for accounts with SUPER (or CONNECTION_ADMIN in 8.0), which is your guaranteed emergency entry, plus an optional dedicated admin_port that bypasses the main listener entirely.

Pools change the math. A Hikari pool with maximumPoolSize 20 on each of 40 pods can open 800 connections against a 400-slot server — and it will, because pools pre-fill to minimumIdle and grow under load. The correct budget is sum(pool_maximums) + headroom for admins, backups, and replicas < max_connections.

When teams skip this inequality, every scale-up or deploy becomes a lottery: it works until pod count crosses the invisible line.

The third actor is the sleeper: a connection in Sleep state holding its slot while the app does nothing. Leaked connections (opened, never closed), oversized minimumIdle, and 8-hour default wait_timeout let sleepers accumulate for a whole workday. SHOW PROCESSLIST exposes them — user, host, db, Time in Sleep — and wait_timeout plus server-side pruning bounds how long they can camp.

Plain-English First

Think of MySQL as a parking lot with 151 spaces. Each app server runs a valet pool that grabs spaces early — and after a deploy, forty valets each holding twenty spaces need 800 spots for a 400-space lot. Error 1040 is the FULL sign. Most taken spaces hold empty parked cars: Sleep connections doing nothing. The fix is towing the empties (KILL), shrinking each valet's reservation (pool size), expiring abandoned cars faster (wait_timeout), and keeping one staff gate (SUPER admin) open.

ERROR 1040 (HY000): Too many connections. It hits at the worst moment — a deploy finishes, traffic shifts, and the app can't open a single database connection. Dashboards show the database CPU relaxed and queries fast, yet every new connection dies instantly. The server isn't overloaded; it's fully booked.

The usual culprit isn't traffic, it's arithmetic. max_connections caps total slots (151 by default), while every app instance holds a pool of idle-ready connections. Multiply one generous pool setting by forty pods and you oversubscribe the server before serving a single real query. Sleep-state threads — open, authenticated, doing nothing — pile up until the last slot goes. The same math bites cron jobs, replicas, and consoles sharing the server, so count every client in the budget.

This guide covers the emergency drill and the durable fix: confirming saturation, reading SHOW PROCESSLIST like a roster, getting in through the SUPER back door, pruning sleepers, tuning wait_timeout, and sizing pools with explicit math so deploys stop triggering 1040s for good.

Error 1040 Is Arithmetic, Not Traffic

Treat 1040 as a capacity equation with three terms: demand (sum of all pool maximums plus humans, crons, and replicas), supply (max_connections), and headroom for the unexpected. When demand exceeds supply, MySQL refuses new connections while existing ones run fine — which is why CPU looks calm and slow logs look clean during a total outage. The three status queries below separate a full lot from a slow server in seconds.

Threads_connected is the current occupancy; Max_used_connections is the historical peak since restart; max_connections is the ceiling. A Threads_connected pinned at the ceiling with low CPU is the 1040 fingerprint. Compare Max_used_connections against the ceiling too: if the peak just started touching the limit after a deploy, the deploy changed demand — count pods and pools, not queries.

Memory is why you can't just pave a bigger lot. Each connection carries thread stack plus sort, join, and read buffers — gilbally small per thread, significant at 800 threads. Raising max_connections without raising RAM converts refused connections into swapping and OOM kills, which take down existing connections too. Size the ceiling for memory, then fit demand inside it. Monitor the ratio, not just the errors — occupancy creeping from 40% to 65% across releases warns you weeks before the first 1040.

confirm_1040.sqlSQL
1
2
3
4
5
6
7
8
9
-- Supply vs demand in three numbers
SHOW VARIABLES LIKE 'max_connections';
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Max_used_connections';

-- Occupancy ratio (alert above 0.75 in monitoring)
SELECT VARIABLE_VALUE AS threads_connected
FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Threads_connected';
📊 Production Insight
A team raised max_connections 400→800 and 'recovered' for 6 minutes — demand was 800+. The ceiling moved; the arithmetic didn't. Pruning and pool cuts fixed it permanently.
🎯 Key Takeaway
Diagnose 1040 with three numbers (ceiling, current, peak) and fix demand first — bigger ceilings without pool math just move the cliff.

SHOW PROCESSLIST: Sleepers Are the Usual Suspects

SHOW FULL PROCESSLIST is the roster of every slot holder: id, user, source host, db, Command, Time, and State. During a 1040, sort mentally by Command. Sleep with a large Time means an authenticated connection doing nothing — a pool reservation, a leaked handle, or a console someone left open. Query means real work; a handful of long Queries among hundreds of Sleepers proves the server is idle-rich and slot-poor.

Group to find the owner fast. The aggregation query below collapses hundreds of rows into per-source counts: one app host with 300 Sleep rows is the misconfigured pool; fifty cron hosts with 2 each is a leak pattern. The db column adds the schema, and Time shows how long each sleeper has camped — Time values in the thousands next to an 8-hour wait_timeout mean threads that will never leave on their own.

Save the grouped output before killing anything. It justifies the pool change in the postmortem ('service X held 312 of 400 slots in Sleep') and tells you exactly which deploy to fix first. Kill from evidence, not from vibes. Rerun the grouped query after every prune batch — the shrinking top row confirms you're draining the right pool. If two sources tie, fix the one with the oldest idle seconds first: ancient sleepers signal leaks, fresh ones signal oversized minimums.

roster_sleepers.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- Full roster (id, user, source, db, command, idle seconds)
SHOW FULL PROCESSLIST;

-- Grouped: who holds the slots?
SELECT user, SUBSTRING_INDEX(host, ':', 1) AS src,
  command, COUNT(*) AS n,
  MAX(time) AS max_idle_s
FROM information_schema.processlist
GROUP BY user, src, command
ORDER BY n DESC;

-- Kill candidates: idle Sleepers over 10 minutes
SELECT CONCAT('KILL ', id, ';')
FROM information_schema.processlist
WHERE command = 'Sleep' AND time > 600;
📊 Production Insight
One grouped query showed a single reporting host holding 312 Sleep slots — a client that never closed read-only handles. The leak fix freed more slots than the ceiling raise ever did.
🎯 Key Takeaway
Read PROCESSLIST as a roster: group by source and command, and let the biggest Sleep group name the pool to fix.

Getting In When the Door Is Shut: SUPER and the Admin Port

MySQL always keeps one extra connection slot for SUPER-privileged accounts (CONNECTION_ADMIN in 8.0) beyond max_connections. That +1 is your fire escape: a break-glass admin credential connects while the app gets 1040s, letting you diagnose and prune from inside. It only works if the credential exists, has the privilege, and someone tested it — an untested fire escape is decoration.

MySQL 8 adds a second door: the admin port (admin_port, commonly 33062), a separate listener with its own thread pool and TLS, configured via admin_address. Connections there bypass the main listener's saturation entirely, which matters when even the reserved slot is contended. Configure it on every production node and probe it from monitoring so a dead admin port pages before the incident.

On managed services the mechanics differ but the principle holds. RDS has no SUPER; instead use the master user (which gets the reserved slot via its own privilege) and the rdsadmin kill procedures. Know your platform's equivalent before the outage: the docs page you read at 10 AM beats the one you skim at 2 AM. After entry, run the occupancy queries before any KILL — knowing Threads_connected versus the ceiling tells you how many sleepers must die, so you prune once instead of in panicked rounds.

emergency_entry.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
# Fire escape 1: reserved slot for privileged accounts (+1 over max)
mysql -u breakglass -p -h db-primary -e 'SELECT 1;'

# Fire escape 2: dedicated admin port (MySQL 8, if configured)
mysql -u admin -p -h db-primary --port=33062 -e 'SHOW FULL PROCESSLIST;'

# From inside: confirm saturation, then prune oldest sleepers
# mysql> SHOW STATUS LIKE 'Threads_connected';
# mysql> KILL <id>;  (Sleep rows only — never blind active queries)

# RDS equivalent (no SUPER): master user + rds kill procedures
# mysql> CALL mysql.rds_kill(thread_id);
⚠ Never Blanket-KILL Active Queries
KILLing Query-state threads mid-write triggers rollbacks that hold worse locks while unwinding. Prune Sleep rows freely; leave running work alone unless you've checked what it is.
📊 Production Insight
The break-glass credential hadn't been rotated in a year and failed at first — 8 extra minutes of full outage. Monthly fire-drill logins are now a checklist item.
🎯 Key Takeaway
Keep a tested SUPER credential and admin port as your guaranteed entry — then prune sleepers from inside, never active work blindly.

wait_timeout: Stop Feeding Abandoned Threads

wait_timeout is how long the server tolerates an idle (Sleep) connection before closing it: 28,800 seconds — 8 hours — by default. That default assumes diligent clients; with leaky ones it means every abandoned handle camps until end of business day. Dropping it to 300 seconds bounds any leak to 5 minutes of slot occupancy, which turns most 1040s from outages into blips the pool absorbs.

Set both wait_timeout (TCP clients) and interactive_timeout (console clients) together, or consoles keep the old behavior while you wonder why nothing changed. Apply with SET GLOBAL for immediate effect plus the my.cnf entry for survival across restarts — GLOBAL-only changes evaporate on failover, and failovers love happening the week after you forget. Verify from a fresh session, since existing connections keep the value they negotiated at login.

Treat the timeout as a seatbelt, not a fix. It bounds damage while you repair the leak: unclosed handles in code, minimumIdle set to maximum, health checks opening connections they never close. The timeout keeps you alive; the code review keeps you healthy. Document the standard timeout in the base my.cnf template so new replicas inherit it — a failover that resurrects 8-hour timeouts reopens the leak the week after you closed it.

tune_wait_timeout.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
-- Current idle tolerance (28800 = 8h default)
SHOW VARIABLES LIKE 'wait_timeout';
SHOW VARIABLES LIKE 'interactive_timeout';

-- Bound abandoned threads to 5 minutes (immediate + persistent)
SET GLOBAL wait_timeout = 300;
SET GLOBAL interactive_timeout = 300;
-- Also add to my.cnf: wait_timeout=300 / interactive_timeout=300

-- Fresh sessions pick it up; verify:
SELECT @@GLOBAL.wait_timeout, @@SESSION.wait_timeout;
📊 Production Insight
Cutting wait_timeout 8h→5min turned a daily 1040 into a non-event while the leak fix worked through review — the seatbelt held for two weeks.
🎯 Key Takeaway
Drop wait_timeout to minutes as a seatbelt, persist it in my.cnf, and fix the underlying leak in code review.

Pool Math: Size Hikari Below max_connections

HikariCP rewards explicit budgets. maximumPoolSize caps per-instance connections; minimumIdle sets how many stay open when idle; setting minimumIdle equal to maximum (a common copy-paste) means every pod holds the max forever — the 40×20=800 disaster. Sensible production values are maximumPoolSize 8–10 and minimumIdle 2 for typical services: pools burst under load and shrink when quiet, which is the entire point of pooling.

Do the fleet math in the deploy pipeline, not in your head. Sum maximumPoolSize across every service times its pod count, add 20 for admins, backups, exporters, and replicas, and require the total under 70% of max_connections. The 30% headroom absorbs failovers (replicas promote with their own pools), cron spikes, and the odd console. The script below computes the budget from live pod counts and writes the corrected properties.

Watch connectionAcquisitionTimeout too: when the pool (not the server) is the bottleneck, threads wait for a pool slot instead of failing fast. A 30-second acquisition timeout with pool-exhaustion metrics tells you to grow the pool slightly or add read replicas — before users feel it as latency. Recheck the budget every quarter: pod autoscaling moves the left side silently, and yesterday's comfortable 50% is tomorrow's 85%.

pool_budget.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
# Fleet pool budget: sum(maximumPoolSize x pods) + 20 headroom < 70% of max
set -euo pipefail
MAX_CONN=$(mysql -u monitor -h db-primary -N -e "SELECT @@GLOBAL.max_connections;")
BUDGET=$(python3 -c "print(int($MAX_CONN * 0.7 - 20))")
echo "max_connections=$MAX_CONN -> pool budget for apps: $BUDGET"
# Example: 40 pods x maximumPoolSize 8 = 320 (fits a 400 budget)
cat > application.properties << 'EOF'
spring.datasource.hikari.maximum-pool-size=8
spring.datasource.hikari.minimum-idle=2
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.max-lifetime=1800000
EOF
echo "wrote application.properties (max 8, idle 2)"
💡minimumIdle Is the Silent Killer
minimumIdle equal to maximumPoolSize means every pod permanently holds the max. Drop minimumIdle to 2 so quiet pods release slots — bursting still works because the pool regrows under load.
📊 Production Insight
Cutting maximumPoolSize 20→8 and minimumIdle 20→2 dropped fleet demand from 800 to ~120 steady-state — the same traffic, one-seventh of the slots.
🎯 Key Takeaway
Budget pools fleet-wide with the 70% rule, keep minimumIdle tiny, and enforce the math in the deploy pipeline.

Prevention: Alerts, Reviews, and Load Tests

Page on occupancy, not on failure. Alert when Threads_connected crosses 75% of max_connections with a 5-minute sustain — that fires while you still have slots to investigate, unlike the 1040 itself. Track Max_used_connections across deploys: a step-change in the peak the morning after a release names the guilty deploy before users do. Export both to your dashboards next to pool-active-threads so app-side and server-side views agree.

Review connection behavior in code like any resource: every opened handle closed in finally blocks, no per-request connections when a pool exists, health checks reusing the pool instead of opening fresh sockets. Load tests should assert on Threads_connected growth per RPS — a service whose slots scale with traffic instead of staying flat has a leak that production will find at the worst hour.

Rehearse the drill quarterly. A game day that fills the lot in staging (lower max_connections, oversized test pools) and walks the team through SUPER entry, sleeper pruning, and pool cuts turns the 3-minute recovery from luck into procedure. The lottery only pays teams that bought a ticket in advance. Record the drill's timings — entry, prune, pool cut — so the next incident runs a proven playbook with real numbers.

watch_conn_saturation.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
# Cron every 2 min: warn at 75% occupancy, page with top holders
set -euo pipefail
read -r MAX_CONN CONN < <(mysql -u monitor -h db-primary -N -e \
  "SELECT @@GLOBAL.max_connections;" -e "SHOW STATUS LIKE 'Threads_connected';" \
  2>/dev/null | awk '{print $2}' | tr '\n' ' ')
PCT=$(python3 -c "print(int(100*$CONN/$MAX_CONN))")
if [ "$PCT" -ge 75 ]; then
  mysql -u monitor -h db-primary -e "
  SELECT user, SUBSTRING_INDEX(host,':',1) AS src, command, COUNT(*)
  FROM information_schema.processlist GROUP BY user, src, command;"
  echo "ALERT: connection occupancy ${PCT}% ($CONN/$MAX_CONN)"
  exit 1
fi
echo "OK: occupancy ${PCT}% ($CONN/$MAX_CONN)"
📊 Production Insight
A 75% occupancy alert now fires 20 minutes before the lot fills — the last three near-misses were pool cuts during business hours, not 2 AM pages.
🎯 Key Takeaway
Alert at 75% occupancy, review connection hygiene in code, and rehearse the prune-and-cut drill before you need it.
● Production incidentPOST-MORTEMseverity: high

A Deploy Scaled to 40 Pods and Booked 800 Slots on a 400-Slot Server

Symptom
At 10:05 AM a routine deploy scaled the checkout fleet from 12 to 40 pods for a sale. By 10:09 AM checkout errored 100% with ERROR 1040 — zero new connections accepted, 412 threads connected against max_connections 400. Reads, writes, health checks: all dead, since even the health check needed a connection. The database showed 18% CPU. Rollback was considered but the old pods were already draining, and redeploying the old build would rescale through the same broken math.
Assumption
The team assumed a traffic surge had saturated the database and raised max_connections from 400 to 800 live. Connections briefly succeeded, then failed again at the new ceiling within 6 minutes — because 40 pods × 20 pool slots = 800 plus cron jobs and replicas now exceeded 800. Raising the ceiling without touching demand just moved the cliff, while each new thread added memory pressure that pushed the box toward swap.
Root cause
Pool arithmetic nobody had written down: Hikari maximumPoolSize 20, minimumIdle 20, across 40 pods = 800 potential connections against max_connections 400. minimumIdle 20 meant every pod held 20 connections even idle, and 70% of the 412 connected threads sat in Sleep with Time values over 1,000 seconds — leaked slots from a client that never closed read-only handles. The deploy didn't add traffic; it added valets.
Fix
They entered through a SUPER admin account (the +1 reserved slot), KILLed 190 threads sleeping over 600 seconds, and service recovered in 3 minutes. Then they cut Hikari to maximumPoolSize 8 / minimumIdle 2 fleet-wide, set wait_timeout to 300 seconds, and reverted max_connections to 400. A pool-budget check (sum of maximums × pods < 70% of max_connections) joined the deploy pipeline, and Threads_connected alerting pages at 75%.
Key lesson
  • Write down the pool inequality and enforce it in CI: sum of every pool maximum times pod count plus headroom must stay under max_connections, or scale-ups become outages.
  • Keep the emergency door tested: a SUPER admin credential plus the admin port, exercised monthly, is the difference between a 3-minute prune and a 22-minute lockout.
  • Attack demand before ceiling: pruning sleepers and shrinking pools recovers service in minutes, while raising max_connections just relocates the cliff and adds memory risk.
Production debug guideSix steps from locked-out to recovered, in an order that never makes it worse.6 entries
Symptom · 01
App logs show ERROR 1040 on every new connection
→
Fix
Confirm saturation with numbers, not vibes: SHOW VARIABLES LIKE 'max_connections'; then SHOW STATUS LIKE 'Threads_connected'; and SHOW STATUS LIKE 'Max_used_connections'; If Threads_connected equals max_connections, the lot is full. Note Max_used_connections — if the peak is far below traffic peaks, the surge is new demand (usually pool-related), not organic growth.
Symptom · 02
You need to see who holds all the slots
→
Fix
Run SHOW FULL PROCESSLIST; (or SELECT user, host, db, command, time, state FROM information_schema.processlist;) and tally by Command: hundreds of Sleep rows with large Time values are idle holders, while rows in Query are real work. Group them: SELECT user, SUBSTRING_INDEX(host,':',1) AS src, command, COUNT(*) FROM information_schema.processlist GROUP BY user, src, command; The biggest Sleep group names the misconfigured pool.
Symptom · 03
You're locked out — even admin logins get 1040
→
Fix
Use the reserved slot: connect with an account holding SUPER or CONNECTION_ADMIN — MySQL keeps one extra connection beyond max_connections for it: mysql -u breakglass -p -h db-primary -e 'SELECT 1'; On MySQL 8 you can also use the dedicated admin interface: mysql -u admin -p -h db-primary --port=33062. Test this path monthly; discovering it doesn't work during an outage is its own incident.
Symptom · 04
Inside via SUPER — reclaim slots without killing real work
→
Fix
Kill idle holders only: SELECT CONCAT('KILL ',id,';') FROM information_schema.processlist WHERE command='Sleep' AND time > 600; Review the list, then execute it in batches. Never blanket-KILL Query-state threads — murdering a 10-minute migration mid-write buys a rollback storm. Re-check Threads_connected after each batch; stop as soon as the app reconnects.
Symptom · 05
Sleepers refill as fast as you kill them
→
Fix
Shorten their lifespan: SHOW VARIABLES LIKE 'wait_timeout'; — the 28800 default lets abandoned threads camp 8 hours. Set SET GLOBAL wait_timeout = 300; (and interactive_timeout to match) so idle threads die in 5 minutes. This bounds the leak while you fix the app side; it doesn't replace closing connections properly.
Symptom · 06
Recovered — now stop the next deploy from repeating it
→
Fix
Do the pool math on the spot: sum every service's pool maximum × its pod count, add 20 for admins/backups/replicas, and compare against max_connections. Cut Hikari maximumPoolSize (8 is plenty for most services) and minimumIdle (2), redeploy one service, and watch SHOW STATUS LIKE 'Threads_connected'; fall. Then codify the inequality as a deploy gate.
MySQL 1040 Causes at a Glance
Root CauseHow to ConfirmFixPrevention
Pool maximums exceed max_connectionsSum(pool max × pods) > max_connectionsCut maximumPoolSize/minimumIdle fleet-wide70% budget gate in deploy pipeline
Leaked Sleep connectionsPROCESSLIST grouped: huge Sleep counts, big TimeKILL Sleep > 600s; fix unclosed handlesfinally-block hygiene; occupancy alerts
8-hour default wait_timeoutwait_timeout=28800; sleepers camp all daySET GLOBAL wait_timeout=300 + my.cnfStandardize 300s in base configs
Deploy/scale-up adds pods, not trafficMax_used_connections steps up with releaseRoll pool settings with the deployLoad-test slot growth per RPS
No privileged entry during lockoutBreak-glass login also gets 1040SUPER reserved slot / admin port / RDS masterMonthly fire-drill login test
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
confirm_1040.sqlSHOW VARIABLES LIKE 'max_connections';Error 1040 Is Arithmetic, Not Traffic
roster_sleepers.sqlSHOW FULL PROCESSLIST;SHOW PROCESSLIST
emergency_entry.shmysql -u breakglass -p -h db-primary -e 'SELECT 1;'Getting In When the Door Is Shut
tune_wait_timeout.sqlSHOW VARIABLES LIKE 'wait_timeout';wait_timeout
pool_budget.shset -euo pipefailPool Math
watch_conn_saturation.shset -euo pipefailPrevention

Key takeaways

1
Error 1040 is demand beating supply
diagnose with ceiling, current, and peak numbers.
2
Group PROCESSLIST by source and command
the biggest Sleep group names the broken pool.
3
Enter through the tested SUPER slot or admin port, and prune Sleep rows only.
4
Bound leaks with wait_timeout 300s, persisted in my.cnf, not just GLOBAL.
5
Budget pools fleet-wide
sum of maximums × pods plus headroom under 70% of max.
6
Alert at 75% occupancy and rehearse the drill so recovery takes minutes.

Common mistakes to avoid

5 patterns
×

Raising max_connections to 2000 and calling it fixed

Symptom
1040s stop, then the box swaps and OOM-kills mysqld — every connection costs RAM.
Fix
Size max_connections for memory; fit demand inside it with pool budgets instead.
×

Blanket-KILLing Query-state threads to free slots

Symptom
Slots free briefly, then rollback storms hold worse locks and writes stall.
Fix
Kill Sleep rows only; investigate running work before touching it.
×

Setting minimumIdle equal to maximumPoolSize everywhere

Symptom
Every pod permanently holds its max; fleet demand equals worst case at all times.
Fix
minimumIdle 2, maximum 8–10: burst under load, release when quiet.
×

No tested break-glass credential

Symptom
Full lockout with no way in — recovery waits for a restart or traffic drop.
Fix
Provision SUPER admin + admin port; exercise the login monthly from runbooks.
×

Trusting the 8-hour wait_timeout default

Symptom
Abandoned threads accumulate all day; the lot fills by afternoon.
Fix
Standardize wait_timeout 300s globally and persist it in my.cnf.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What does MySQL error 1040 mean?
Q02SENIOR
Show me the pool math that prevents 1040s.
Q03SENIOR
PROCESSLIST shows 300 Sleep rows with huge Time values. What are they?
Q04SENIOR
You're fully locked out with 1040 on every login. How do you get in?
Q05SENIOR
When is raising max_connections the wrong fix?
Q01 of 05JUNIOR

What does MySQL error 1040 mean?

ANSWER
All max_connections slots are taken, so the server refuses new connections while existing ones run fine. It's a capacity refusal — calm CPU with total connection failure is the fingerprint.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
What should max_connections be?
02
Why didn't restarting MySQL fix our 1040s?
03
Is it safe to KILL Sleeping connections?
04
What Hikari settings prevent this?
05
How do I handle this on RDS without SUPER?
06
Which two metrics should I alert on?
N
Naren Founder & Principal Engineer

20+ years shipping high-throughput database systems. Lessons pulled from things that broke in production.

Follow
✓ Verified
production tested
September 24, 2026
last updated
1,942
articles · all by Naren
🔥

That's MySQL. Mark it forged?

5 min read · try the examples if you haven't

←
Previous
MySQL Incorrect String Value Fix
4 / 7 · MySQL
Next
MySQL 1062 Duplicate Entry Fix
→