TCP连接状态里的TIME_WAIT本是一种正常的协议行为,但当一台应用服务器或数据库服务器上堆积了成千上万个TIME_WAIT连接时,各种怪问题就来了:新连接偶发失败、MySQL响应变慢、压测吞吐量上不去。很多团队第一反应是去优化SQL、加索引,结果折腾半天毫无改善。这篇文章就来把这个问题彻底讲清楚,从TIME_WAIT的产生机制、排查方法到内核参数和连接池的优化方案,给你一套完整的解决思路。

TIME_WAIT是怎么产生的,为什么会影响MySQL查询
TCP连接断开时需要经过四次挥手。谁先主动关闭连接,谁就会进入TIME_WAIT状态,并停留2MSL的时间(Linux下默认约60秒)。这个设计的目的是让最后的ACK能可靠到达对方,同时防止旧连接的报文混入新连接。所以TIME_WAIT本身不是bug,而是协议自我保护的机制。
问题出在短连接模式上。如果应用程序每次执行SQL都新建一个MySQL连接、执行完立刻断开,那么每秒1000次查询就意味着每分钟产生6万个TIME_WAIT状态的socket。Linux为每个对外连接分配一个本地端口,可用端口范围默认只有约2.8万个(32768-60999),一旦端口耗尽,新连接就会报"Cannot assign requested address"错误。而在端口耗尽之前,内核维护海量TIME_WAIT socket本身就要消耗CPU和内存,连接建立的三次握手次数暴增也会让MySQL花费大量精力在连接管理上,真正执行查询的资源反而被挤压,表现出来就是查询变慢。
还有一个容易被忽略的点:很多框架内部用了重试机制,连接失败后立刻重试,形成雪崩效应,让TIME_WAIT堆积得更快。所以判断根因时,先看连接模式是短连接还是长连接,往往能少走很多弯路。
如何定位TIME_WAIT来源和确认影响范围
排查的第一步是统计当前TIME_WAIT的数量和来源。用ss命令比netstat更高效,数据量大时差距明显:
# 统计TIME_WAIT总数
ss -ant state time-wait | wc -l
# 按目标地址分组,看TIME_WAIT都连到了哪里
ss -ant state time-wait dst 192.168.1.100:3306 | head -20
# 老工具写法,效果类似
netstat -ant | grep TIME_WAIT | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn
如果发现TIME_WAIT大量指向MySQL的3306端口,且来源IP是应用服务器,基本可以确认是应用侧短连接频繁创建销毁导致的。接着在MySQL侧确认连接建立频率,观察Threads_connected和Connections计数器的变化速度:
SHOW GLOBAL STATUS LIKE 'Threads_connected'; SHOW GLOBAL STATUS LIKE 'Connections'; -- 间隔10秒再查一次Connections,差值除以10就是每秒新建连接数
如果每秒新建连接数达到几百上千,慢的根源就找到了。还要顺手检查应用侧是否报过端口耗尽错误,查看dmesg日志中有没有"possible SYN flooding"或应用日志中的连接超时记录。另外用show processlist看看是否有大量线程处于频繁创建销毁的状态,这些证据链拼起来,问题定位就非常扎实了。
内核参数调优:让系统更从容地处理TIME_WAIT
应急处理或者无法立即改造应用时,内核参数调优是最直接的手段。以下几个参数是重点:
# 编辑 /etc/sysctl.conf,加入以下配置 # 开启TIME_WAIT端口复用,仅对客户端角色(主动发起连接方)生效 net.ipv4.tcp_tw_reuse = 1 # 扩大可用端口范围,缓解端口耗尽 net.ipv4.ip_local_port_range = 10240 65000 # 缩短FIN_WAIT_2超时时间,减少半关闭连接堆积 net.ipv4.tcp_fin_timeout = 30 # 启用快速回收TIME_WAIT(仅建议在NAT环境外使用,需谨慎) # net.ipv4.tcp_tw_recycle = 1 # 生效 sysctl -p
这里要特别说明几点。tcp_tw_reuse开启后,内核允许复用超过1秒的TIME_WAIT端口用于新的对外连接,对应用侧主动关闭连接的场景非常有效,且在内核3.x之后配合时间戳使用是安全的。而tcp_tw_recycle在内核4.12之后已经被移除,因为它在NAT环境下会导致大量连接异常,生产环境不要尝试。扩大ip_local_port_range是治标手段,把可用端口从2.8万扩到5万以上,能显著推迟端口耗尽的时间点。
需要注意,如果你的TIME_WAIT堆积在MySQL服务器一侧(即MySQL主动关闭连接,比如wait_timeout超时踢掉空闲连接),那么tcp_tw_reuse帮助有限,因为MySQL是被动接受连接的一方,端口复用参数对监听方不适用。这种情况下重点要调整MySQL的wait_timeout,避免服务端主动踢掉还活着的连接。
应用层与MySQL侧的根治方案
内核调优终究是缓解手段,根治还得靠连接池。主流语言都有成熟的连接池实现:Java用HikariCP或Druid,Go用database/sql自带的连接池,Python用SQLAlchemy的pool模块。以Java的HikariCP为例,推荐配置如下:
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://192.168.1.100:3306/mydb");
config.setUsername("app");
config.setPassword("secret");
// 连接池大小不是越大越好,经验公式参考:CPU核数 * 2 + 磁盘数
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
// 空闲连接存活时间,要小于MySQL的wait_timeout
config.setIdleTimeout(300000);
config.setMaxLifetime(600000);
config.setConnectionTimeout(3000);
HikariDataSource ds = new HikariDataSource(config);
配置连接池时有个关键细节:连接的最大存活时间(maxLifetime)必须小于MySQL的wait_timeout,否则会出现连接池里的连接已被服务端踢掉、应用还拿着失效连接去查询的情况,表现为偶发的"MySQL server has gone away"。反过来,MySQL侧的wait_timeout也不宜设得太小,如果应用确实需要长空闲连接,可以适当调大:
# my.cnf中配置 [mysqld] wait_timeout = 900 interactive_timeout = 900 max_connections = 2000
最后补充一点,如果架构上有多层代理(比如应用经过LB或ProxySQL再到MySQL),每一跳都可能产生TIME_WAIT,排查时要逐层确认是谁在主动关闭连接。ProxySQL这类中间件自带连接复用能力,也是缓解短连接问题的有效方案。总结一下:先用ss命令确认TIME_WAIT来源,短期靠内核参数顶住,长期靠连接池加合理的超时配置根治,三层配合才能真正让MySQL查询恢复流畅。
mysql TIME_WAITtcp连接优化查询慢排查修改时间:2026-09-07 15:51:12