← Debugging Guides

阅读中文版本

MySQL — Aborted connection ... Got timeout reading communication packets

Investigate MySQL aborted connection warnings, separate client-side abandonment from network issues, and align wait_timeout with pooled connection lifecycles.

intermediatedebuggingmysqlconnectionsnetwork

Symptoms

  • [Note] Aborted connection 1234 to db: got timeout reading communication packets floods the error log
  • Aborted_clients climbs while connections seem healthy from the application perspective
  • Intermittent query failures correlate with connection pool recycling or idle periods

Possible causes

  • Pooled connections idle longer than wait_timeout, so the server closes them while the pool still believes they are alive
  • Clients abandon queries mid-response (user canceled, request deadline hit) without closing cleanly
  • Network middleboxes (NAT, firewalls, LBs) silently drop idle TCP streams, breaking the next packet exchange
  • Oversized packets or max_allowed_packet mismatches abort mid-transfer

Diagnosis steps

Quantify and classify the aborts

mysql -e "SHOW GLOBAL STATUS LIKE 'Aborted%';" && tail -100 /var/log/mysql/error.log | grep "Aborted connection" | head -5

Interpretation

Aborted_clients counts server-side closes (often timeout); Aborted_connects counts failed handshakes (credentials, limits) — different problems.

Compare idle settings with pool behavior

mysql -e "SHOW VARIABLES LIKE 'wait_timeout';" && grep -Rn "idleTimeout\|maxLifetime\|keepalive" app-config/

Interpretation

A pool idle timeout longer than wait_timeout guarantees the server reaps live pooled connections; maxLifetime shorter than wait_timeout avoids it.

Check for middleboxes on the path

conntrack -S 2>/dev/null | head; ss -tan state established "( dport = :3306 )" | head

Interpretation

Long-lived silent streams through NAT or firewalls get dropped between the endpoints; TCP keepalive on the path prevents it.

Root cause

The server closes connections that exceed wait_timeout without an application-visible reset until the next use. Pools holding such connections then send a query into a dead stream and receive a broken-pipe error, while the server logs an aborted connection note. The two sides disagree about who owns the lifecycle.

Fix

Set pool maxLifetime below wait_timeout so the client always retires first, enable pool keepalive pings under the idle threshold, align application deadlines with query timeouts so clients cancel server-side, and keep TCP keepalive sane on paths crossing stateful middleboxes.

Risk note

Raising wait_timeout to a huge value converts idle capacity into DoS surface and stale connections. Prefer retiring on the client side.

Verify the fix

After alignment, Aborted_clients growth stops under steady load, pool metrics show orderly retirement, and error log noise disappears.

Symptom walkthrough

Classify before debugging — the two counters mean different failures:

SHOW GLOBAL STATUS LIKE 'Aborted%';
Aborted_clients: 18402    <- server closed established connections (timeout class)
Aborted_connects: 3       <- handshakes failed (auth/limits class)

For the timeout class, the log note names the phase:

[Note] Aborted connection 1234 to db: 'shop' user: 'app'
host: '10.0.4.21' (Got timeout reading communication packets)

Reading timeout means the connection sat idle past wait_timeout. The pattern to check: does the growth rate track pool recycling cadence? When the pool idle timeout exceeds the server timeout, the server reaps connections the pool still trusts, and the next borrower pays with a broken pipe.

Fix in practice

Make the client always retire first:

# HikariCP example
spring:
  datasource:
    hikari:
      max-lifetime: 480000        # 8 min < wait_timeout default
      keepalive-time: 120000       # ping idle connections
      minimum-idle: 4
# SQLAlchemy example
create_engine(url, pool_recycle=280, pool_pre_ping=True)

And align the cancel path: when an application deadline fires, cancel the server-side query too, rather than abandoning the socket mid-result — otherwise every timeout becomes one aborted connection note and one wasted server thread.