症状
先分类再调试——两个计数器意味着不同故障:Aborted_clients 增长是服务端关闭了已建立连接(超时类),Aborted_connects 增长是握手失败(凭证/限额类)。对超时类,日志 note 点名阶段:“Got timeout reading communication packets” 即连接闲置超过 wait_timeout。要检查的形态:增长速率是否与连接池回收节奏同步——池的闲置超时大于服务器超时时,服务器收割池还信任的连接,下一个借出者以断管买单。
诊断思路
先量化并分类两类中止;再对比 wait_timeout 与池配置(idleTimeout/maxLifetime/keepalive),池闲置超时更长就必然出现服务端先杀;最后检查路径上的中间盒(NAT、防火墙、LB)对静默 TCP 流的丢弃——ss 查看存活流、conntrack 查看状态表。max_allowed_packet 不匹配会在传输中途中止,是第四类。
根因
服务器对超过 wait_timeout 的连接做关闭,而下一次使用之前应用侧毫无感知。持有这类连接的池把查询写进死流、收到断管错误,同时服务器记一条 aborted connection——两端对“谁拥有生命周期”意见不一致。
修复与验证
让客户端永远先退役:池 maxLifetime 设在 wait_timeout 之下(如 8 分钟 < 默认 8 小时),keepalive 在闲置阈值内发心跳(HikariCP 的 keepalive-time、SQLAlchemy 的 pool_pre_ping);对齐取消路径——应用截止时间触发时同时取消服务端查询,而不是丢下套接字半途而废,否则每次超时都变成一条中止日志加一个浪费的服务器线程。验证时稳态负载下 Aborted_clients 停止增长、池指标显示有序退役、错误日志噪音消失。把 wait_timeout 调成天文数字只是把闲置容量换成 DoS 面加陈旧连接——让客户端先退役才是正解。
代码示例
SHOW GLOBAL STATUS LIKE 'Aborted%';
Aborted_clients: 18402 <- server closed established connections (timeout class)
Aborted_connects: 3 <- handshakes failed (auth/limits class)
[Note] Aborted connection 1234 to db: 'shop' user: 'app'
host: '10.0.4.21' (Got timeout reading communication packets)
# 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)