MySQL报错Too many connections的排查与解决方法(max_connections与连接泄漏定位)

应用日志里突然刷满一行报错,新请求全部连不上库,已经连着的还能正常用。用客户端手工连,也是同样一句:

ERROR 1040 (08004): Too many connections

这类故障十次里有八次不是真的并发太高,而是连接没被释放。

先分清是并发高还是连接堆着

还能连进去的话,几条命令就能看清现状:

show variables like 'max_connections';
show status like 'Threads_connected';
show status like 'Threads_running';
show status like 'Max_used_connections';

MySQL 5.7和8.0的max_connections默认值都是151。如果Threads_connected贴着151,而Threads_running只有3、4个,说明绝大多数连接处于Sleep状态,机器根本没在忙,是连接被占着不还。真正并发高的时候,Threads_running会跟着一起涨。

Max_used_connections记的是开机以来的峰值,配合Uptime看,能判断这次是头一回还是老毛病。

看连接都是谁开的

select user,host,command,time,state,left(info,80)
from information_schema.processlist
order by time desc limit 20;

Time列是连接处于当前状态的秒数。一堆Sleep挂着几千秒,八成是应用端的事。按来源IP聚合一下,哪个服务开的连接最多一目了然:

select user,substring_index(host,':',1) as ip,count(*) as c
from information_schema.processlist
group by user,ip order by c desc;

之前遇到过一次,聚合出来只有一台应用服务器占了140个连接,那台机器上刚发过版,连接池配置被覆盖掉了。

连不进去的时候怎么救

连接数打满之后,普通账号也连不上,set global都执行不了。MySQL从8.0.14、8.0.36到8.4.0都有管理端口,专门给这种情况留了一条路,配置文件里加两行,重启生效:

[mysqld]
admin_address = 127.0.0.1
admin_port = 33062

之后用带SERVICE_CONNECTION_ADMIN权限的账号连33062端口,不受max_connections限制。

5.7没有这个东西,只能二选一:停掉一个占用连接最多的应用把连接腾出来,或者直接重启实例。重启前记得先在配置文件里把max_connections改掉,不然起来还是151,半小时后原样再犯。

[mysqld]
max_connections = 1000

改完不用重启也能生效,只是重启会丢:

set global max_connections = 1000;

别盲目往大调

每个连接都会单独分配几个线程级buffer,sort_buffer_size默认256K、join_buffer_size默认256K、read_buffer_size默认128K,这些不是全局共享的。1000个连接全活跃起来,光这几项就能吃掉好几个G。内存8G的机器把max_connections设成3000,等于给自己埋雷,OOM的时候比Too many connections难处理得多。

云服务器上这个值还受规格限制,控制台里改不了的部分,调大也没用。

病根多半在连接池

HikariCP的5.1.0、5.0.1里这几个参数要成对看:maximumPoolSize决定这个实例最多开多少连接,maxLifetime默认1800000毫秒,也就是30分钟,idleTimeout默认600000毫秒。maxLifetime必须小于MySQL服务端的wait_timeout,后者默认28800秒。反过来配的话,服务端单方面把连接掐了,客户端还以为能用,下次取出来就报:

The last packet successfully received from the server was 34,563 milliseconds ago

Druid的1.2.21、1.2.20那边对应的是maxActive、minIdle,另外建议开removeAbandoned配上removeAbandonedTimeout,连接借出去超时不还就强制回收,出问题时能看到是哪段代码借的。

实测下来,一个4核8G的实例上跑着的应用,maximumPoolSize给20到50之间通常就够,开到200反而更慢,连接多了上下文切换和锁竞争都上来了。

顺手清掉堆积的连接

select concat('kill ',id,';')
from information_schema.processlist
where command='Sleep' and time > 600;

把结果复制出来执行。注意kill后面跟的是processlist里的id,不是账号名。MySQL 8.0里这个操作需要CONNECTION_ADMIN权限,5.7是SUPER。

还有几种不是连接池的锅

慢查询把连接占住是最容易误判的一种。这时Threads_running也是高的,show processlist里能看到SQL在执行,去翻慢日志比调连接数有用。另一个常见的是批量任务跑完不关连接,脚本里getConnection之后没有finally,一次漏一点,跑一晚上就攒满了。主从切换之后的瞬间重连风暴也算一种,所有实例同时建连,峰值能到平时的三倍。

防复发就一句话:监控里加Threads_connected除以max_connections的比值,超过0.8就告警,别等ERROR 1040出现在业务日志里。2024年之后各家云厂商的MySQL控制台基本都内置了这个指标,开一下就行。