上周帮朋友排查一个 MySQL 查询超时问题,查来查去发现不是 SQL 写得差,也不是服务器 CPU 爆了,而是内网两台机器之间 TCP 重传率高达 8%,ping 延迟忽高忽低——典型的网络链路不稳。数据库应用对网络延迟和丢包特别敏感,一次连接建立慢半秒,可能就拖垮整个业务请求链路。
先看一眼真实瓶颈在哪
别急着改参数,先用 ss 和 tcpretrans 摸清底细:
ss -i | grep :3306
这条命令能直接看到 MySQL 端口(3306)对应连接的 TCP 信息,重点关注 retrans(重传次数)、rto(重传超时时间)、rtt(往返时延)这三列。如果某条连接 retrans 持续增长,说明中间有丢包或拥塞。
再补一刀:
tcpretrans -C -p :3306
实时抓取 3306 端口上的重传事件,每秒刷新一次。它会告诉你哪个 IP 在重传、重传了多少次——比翻 netstat 日志快多了。
调整 TCP 参数,让数据库连接更‘耐扛’
很多数据库服务器默认用的是通用 TCP 设置,没针对内网高吞吐场景优化。比如 MySQL 主从同步走的是长连接,但系统默认的 tcp_retries2 是 15 次,意味着连续丢包后要等近 15 分钟才断连,期间主库一直在发重传包,白白占带宽。
临时调一下试试:
echo 8 > /proc/sys/net/ipv4/tcp_retries2
这个值设成 8,实际断连时间缩到 2 分钟左右,故障感知更快。再配合降低初始 RTO:
echo 200 > /proc/sys/net/ipv4/tcp_rto_min
让新连接更快响应轻微抖动。注意:这些是运行时生效,重启失效,要永久生效得写进 /etc/sysctl.conf。
别忽略网卡队列和中断绑定
有一次线上 PostgreSQL 连接堆积,netstat -s | grep -i "packet receive" 显示大量 packet receive errors,一查是网卡 RX 队列溢出。多核服务器上,如果所有网卡中断都压在 CPU 0 上,而数据库进程跑在其他核,就会出现「数据到了,但没人及时收」的情况。
用这招看看当前分布:
cat /proc/interrupts | grep eth0
如果只有第 0 列数字大,其他全是 0,基本就是绑核问题。手动把 eth0 的 IRQ 绑定到多个核心(比如 0-3):
echo 0f > /proc/irq/$(grep eth0 /proc/interrupts | awk '{print $1}' | tr -d ':')/smp_affinity_list
0f 是十六进制,对应 CPU 0~3。绑定后,再跑 iftop -P tcp:5432 观察 PostgreSQL 流量是否被多个核分摊处理。
最后顺手关掉干扰项
有些云主机默认开了 tcp_tw_recycle(已废弃)或 tcp_timestamps 配合 NAT 使用时反而引发连接失败。数据库跨 VPC 或走 SLB 时,建议关掉:
echo 0 > /proc/sys/net/ipv4/tcp_tw_recycle
echo 1 > /proc/sys/net/ipv4/tcp_timestamps
后者保留时间戳(用于 RTT 计算),前者彻底禁用易出问题的回收机制。实测某金融客户关掉 tw_recycle 后,MySQL 主从偶发连接中断下降 90%。