PHP连接远程MySQL单连接累计输入超66946字符后无限挂起问题排查
问题根因定位
你遇到的66946字符临界挂起阈值,和PHP、MySQL本身的配置无关,核心是生产服务器到远程MySQL之间的网络链路存在TCP大包丢包问题,和你已经排查排除的PHP代码、MySQL参数、内存限制完全没有关系。
关键特征匹配
- 相同代码在本地WSL、其他同配置Ubuntu主机连同一个远程MySQL时完全正常,直接排除PHP扩展、MySQL服务端参数(比如
max_allowed_packet)的问题 - 挂起而非直接报连接错误,是因为超尺寸的TCP包被中间设备直接丢弃,没有返回任何错误信号,PHP MySQL客户端会一直等待服务端响应,直到TCP层超时(默认需要数分钟,短时间观测就是无限挂起)
- mysqli和PDO的触发阈值略有差异,是因为两个驱动底层的TCP包组包逻辑不同,命中中间设备丢包阈值的时机略有区别
- 不管是单条长查询、还是多条小查询累计到阈值都触发,是因为TCP层的Nagle算法会自动把多次小的写入缓冲区内容合并成一个更大的TCP报文发送,只要合并后的报文长度超过中间设备的丢包阈值,就会出现丢包挂起的情况,和你观测的复现规律完全吻合
排查步骤
- 先确认链路MTU是否匹配:在生产服务器上执行
tracepath 你的MySQL主机IP
查看路径上每一跳的MTU值,找到整条链路的最小MTU值(正常公网无隧道时最小MTU一般为1500,如果走VPN、专线,MTU通常会降到1400甚至更低) - 临时验证MTU问题:找到生产服务器的对外网卡(比如
eth0、ens3),执行ip link set dev 网卡名 mtu 1400
调整完MTU后重新跑你的最小复现脚本,如果传入66947参数时可以正常返回成功,即可100%确认是MTU不匹配导致的丢包 - 排除本地防火墙规则干扰:临时执行
iptables -F清空所有iptables规则,再跑复现脚本,如果恢复正常,说明是本地防火墙配置了单TCP包的载荷长度限制 - 排除SSL链路干扰:如果你的MySQL连接开启了SSL加密,临时改用不加密的方式连接(在
mysqli_connect调用时不传入SSL相关参数),再跑复现脚本,如果恢复正常,说明是链路中间的SSL代理设备存在加密包长度限制
解决方案
长期网络层修复
- 把生产服务器到MySQL链路上的所有设备(本地网卡、中间网关、VPN/专线设备)的MTU值,统一调整为比链路最小MTU低30-40字节(预留TCP/IP头部开销),避免大包被分片或丢弃
- 如果是NAT网关/防火墙的自定义报文长度限制,调整对应规则的最大单包载荷阈值,至少设置为1MB以上
代码层快速绕开(不需要改网络配置)
- 避免把超长文本直接拼接到SQL语句中,改用预处理语句绑定参数:用
mysqli_prepare/PDO::prepare写SQL模板,长文本作为参数通过bind_param传入,这部分内容不会和SQL语法部分打包在同一个TCP报文里,不会触发大包丢包 - 如果业务上必须传超长SQL,直接把单条超过16KB的SQL拆成多次小查询执行,不要在单个连接里发送超过中间设备阈值的大包
内容的提问来源于stack exchange,提问作者Skeets
相关产品推荐
相关产品推荐

