← 中文资源

简体中文版本

MySQL — Aborted connection:读取通信包超时

排查 MySQL 的 aborted connection 警告,区分客户端放弃与网络问题,并让 wait_timeout 对齐连接池生命周期。

阅读英文原文

症状

  先分类再调试——两个计数器意味着不同故障: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)