大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!
有一次半夜被电话叫醒,应用侧疯狂报“连接数据库超时”。
我第一反应是连接池打满了,赶紧连上去看Threads_connected——才300多。max_connections设的是1000。
连接数远没满,为什么连不上?
我盯着监控看了半天,Threads_running也不高,CPU、内存、磁盘全都正常。问题不在数据库内部。
后来在netstat -s | grep -i listen的输出里,看到了一个不断增长的数字:listen queue overflow。
问题找到了:不是MySQL的连接数满了,是TCP连接队列满了。
先把这条链路上的几个概念说清楚:
Threads_connected——MySQL当前已完成握手并accept的连接总数。它只统计“进了门”的连接。
TCP backlog队列——三次握手完成、但MySQL还没执行accept的连接,会排在一个队列里等待。队列长度由MySQL的backlog参数控制。
somaxconn——Linux内核参数(/proc/sys/net/core/somaxconn),限制单个监听套接字的队列最大长度。MySQL的backlog只是“申请值”,实际生效的是min(backlog, somaxconn)。
listen queue overflow——当队列已满,新的SYN包被内核直接丢弃,客户端看到的就是“连接超时”。这个次数可以通过netstat -s查看。
一、连接建立的完整链路
一条TCP连接从建立到被MySQL使用,要经过几个阶段:
第一步:TCP三次握手。 客户端发SYN,服务端回SYN+ACK,客户端回ACK。握手完成后,连接进入accept队列,等待MySQL处理。
第二步:MySQL accept。 MySQL的主线程从accept队列取出连接,分配线程、初始化会话变量、验证权限。这一步完成后,连接才计入Threads_connected。
关键点:第一步完成、第二步还没执行的连接,不在Threads_connected的统计里。它们占着accept队列的位置,但不计入“已连接数”。
这就是“连接数没满却连不上”的核心原因。你看的Threads_connected是300,但accept队列可能已经排了128个等待处理的连接。队列满了,第129个连接直接被丢弃。
二、backlog和somaxconn的配合关系
很多人以为调大MySQL的backlog就完事了。实际上,backlog只是一个“申请值”,最终生效的是它和somaxconn的较小值。
# 查看当前somaxconn
cat /proc/sys/net/core/somaxconn
# 输出:128(很多Linux发行版的默认值)
如果MySQL的backlog设了512,但somaxconn只有128,实际生效的队列长度就是128。你以为调大了,其实没生效。
这个坑非常隐蔽。因为MySQL不会报错,SHOW VARIABLES LIKE 'backlog'也显示512,看起来一切正常。但内核层面只给了128个位置。
排查方法:
# 查看监听队列的溢出次数
netstat -s | grep -i listen
# 输出中关注 "listen queue of a socket overflowed" 的次数
# 查看当前监听队列状态
ss -lnt
# Send-Q列是队列上限,Recv-Q列是当前排队的连接数
# 如果Recv-Q持续接近Send-Q,说明队列快满了
ss -lnt的输出里,Send-Q表示队列上限,Recv-Q表示当前排队的连接数。如果Recv-Q持续接近Send-Q,说明队列压力很大,随时可能溢出。
三、哪些场景最容易触发
场景一:高并发短连接。 PHP应用、Serverless函数、无连接池的脚本——每次请求都新建连接、用完即关。连接建立和销毁的频率极高,accept队列的压力最大。
场景二:应用重启后的连接风暴。 多实例应用同时重启,每个实例的连
接池同时初始化,瞬间产生大量并发连接请求。这时候Threads_connected还没涨上来,但accept队列已经爆了。
场景三:慢accept。 如果MySQL主线程被其他任务阻塞(比如执行某些管理命令、或者系统负载极高),accept的速度变慢,队列积压加剧。
场景四:突发流量。 秒杀、大促开始瞬间,流量突增,连接请求在极短时间内集中涌入。
四、解决方案:内核参数 + 连接池 + 监控
第一步:调大somaxconn。
# 临时生效
sysctl -w net.core.somaxconn=1024
# 永久生效,写入/etc/sysctl.conf
net.core.somaxconn = 1024
第二步:确认MySQL的backlog不小于somaxconn。
SHOW VARIABLES LIKE 'backlog';
如果backlog小于somaxconn,调大backlog(需要重启MySQL或修改配置文件)。
第三步:连接池增加重试机制。
即使队列偶发溢出,应用侧的重试可以兜底。连接池配置合理的connectionTimeout和重试次数,避免单次连接失败直接抛错给用户。
第四步:减少短连接。
能用连接池就用连接池。短连接不仅给accept队列带来压力,还会增加MySQL的线程创建和销毁开销。
第五步:建立监控。
把netstat -s | grep -i listen的overflow次数纳入监控。这个数字持续增长,就是队列溢出的明确信号。同时监控ss -lnt的Recv-Q,作为队列压力的早期预警。
五、小结
“连接数没满却连不上数据库”这个问题,根因往往在TCP连接队列,不在MySQL内部。Threads_connected只统计已accept的连接,排队中的连接不计入。backlog的实际生效值受somaxconn限制,两者必须配合调整。排查的核心是netstat -s的overflow计数和ss -lnt的队列状态。高并发短连接场景最容易触发,连接池重试和减少短连接是应用侧的配合手段。
小耶在手,SQL 不愁
还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽……我们下次见~