1. 先从报错说起Too many connections到底是什么问题1.1 报错出现的典型场景如果你在CentOS上跑MySQL大概率遇到过这种情况某天业务突然告警接口大面积超时打开日志一看满屏都是ERROR 1040 (HY000): Too many connections。浏览器访问管理端直接白屏命令行执行mysql -uroot -p敲完密码也进不去提示一样。那一刻的心情估计跟服务器被攻击了差不多。这个报错本身非常好理解MySQL允许同时建立的客户端连接数已经到了上限新的连接请求直接被拒绝。它不像慢查询那样慢慢拖垮性能而是直接给你一口回绝业务立马断崖式下跌。更麻烦的是因为连接数满了你连进去执行SHOW PROCESSLIST排查问题都困难——管理连接也被算在连接数里面。这就陷入了一个经典的运维死局系统坏了你进不去修进去了也可能因为操作不当把自己唯一的管理通道也堵死。这篇文章不是单纯的参数调优手册而是从CentOS实战角度出发把发现报错-紧急恢复-彻底修复-预防复发整条链路讲清楚。不管你用的是MySQL 5.7还是8.0不管是裸机部署还是跑在云主机上思路都是一样的。如果你是刚接手业务的运维新手或者开发同学需要自己处理数据库偶发故障这篇文章应该能帮你少走不少弯路。1.2 错误背后的机制连接数上限是怎么来的MySQL设计了一个参数叫max_connections它决定了数据库实例能同时接纳的最大客户端连接数量。默认值在不同的版本和配置模板里不一样MySQL 5.7默认通常是1518.0也是151但很多云数据库或者一键安装包会把它调大比如512或者1000。每次客户端发起连接MySQL都会创建一个线程来处理这个连接的请求连接在运行期间会占用内存、文件描述符、线程资源。如果max_connections设得太小高并发下自然不够用设得太大又容易把系统资源耗尽导致OOM或者其他服务不可用。这个参数看上去简单但很多人忽略了一个关键点max_connections不是唯一的限制因素。操作系统的ulimit、thread_cache_size、table_open_cache、innodb_buffer_pool_size等都会影响实际能支撑的连接数。比如你费劲把MySQL的max_connections改成2000但Linux系统对进程能打开的文件句柄数限制是1024那MySQL根本起不来或者启动时报Cant create thread to handle new connection这就是典型的你改了配置但系统不答应。在CentOS上默认的ulimit -n通常是1024这个数值对MySQL来说太保守了。MySQL每建立一个连接就要消耗一个或者多个文件描述符连接数超过1024时即使max_connections允许更多操作系统也会拒绝分配新的文件描述符最终表现还是Too many connections或者类似错误。所以处理这个问题不能只盯着MySQL参数还要看系统层限制。1.3 为什么CentOS上特别容易遇到CentOS作为服务器操作系统部署MySQL非常普遍。但正因为常见很多初始化步骤被忽略了。比如默认安装的MySQLmy.cnf里往往只有datadir、socket等基础配置max_connections用的是编译默认值151。对一个小型网站来说Tomcat连接池20个连接后台管理系统10个连接加起来可能不到50个够用。但一旦业务做活动、被爬虫盯上、或者代码里忘记关闭数据库连接连接数瞬间飙升到几百上千151这个阈值很快被打满。另外CentOS上很多人喜欢用mysqld_safe或者systemctl启动MySQL但对/etc/security/limits.conf这种东西不敏感根本没意识到文件描述符限制会卡脖子。等出了问题网上搜一圈照着改max_connections1000重启后还是报错这时候才想到去看系统日志一查发现是ulimit的问题。这种例子我见得太多了。还有一类是隐性问题MySQL连接数不是一下子到顶的而是缓慢增长后突然爆掉。比如某个接口的数据库操作特别慢每次查询要几秒应用层的连接池不断创建新连接来应对排队请求老连接又因为慢查询迟迟不释放一增一减就在某个时间点把连接数打满。这类问题如果只靠调整max_connections就像是治标不治本——参数调大了慢查询还在只是晚一点爆而已。2. 先救急让服务立刻恢复的三种临时手段2.1 方法一杀掉空闲连接当Too many connections发生时第一反应应该是想办法进到MySQL内部把不用的连接干掉腾出位置。但问题是入口被封死了怎么办好消息是MySQL为这类场景留了一个额外的管理通道mysql -uroot -p --protocolsocket或者用一个特殊的连接方式mysql -uroot -p -S /tmp/mysql.sock如果你的MySQL开启了skip-networking或者socket文件路径不对可以指定--socket参数。更稳妥的做法是如果还有空余连接位置哪怕只有一两个用root账号尝试登录因为MySQL不会对root做连接数限制其实不是这样的max_connections对所有用户都生效包括root。只不过MySQL会额外预留max_connections 1个超级权限连接供管理员使用。也就是说即使连接数满了你仍然可以用root身份通过socket方式登录进去执行SHOW PROCESSLIST来处理。登录进去后第一件事不是急着杀线程而是看看当前都有哪些连接SHOW PROCESSLIST;这个命令会列出所有非睡眠状态的连接。重点关注Command列是Sleep的那些就是空闲连接。它们占着连接数但不干活可以安全杀掉。批量杀空闲连接可以用下面的方式拼接KILL语句SELECT CONCAT(KILL , id, ;) FROM information_schema.processlist WHERE command Sleep AND time 60;把查询结果复制出来执行即可。一般杀掉大部分空闲连接后新的业务连接就能进来了。但要注意如果应用层的连接池会自动重连杀掉空闲连接不会影响正在使用的连接如果是脚本程序持有连接后长时间不释放杀掉它可能导致该连接上的事务回滚但总比整个服务瘫痪强。2.2 方法二重启MySQL不推荐但有效如果连root都登不进去或者进去之后想杀进程却发现连接数一直在增长杀完一秒又满了这时候最粗暴的恢复手段就是重启MySQL服务systemctl restart mysqld或者用传统的service mysqld restart重启MySQL会强制释放所有连接让数据库恢复可用状态。但代价是所有正在执行的事务会回滚缓存失效连接池要重新建立连接可能对业务造成二次冲击。而且如果你的MySQL是因为参数配置不当导致连接数打满重启后过一会儿还是会打满治标不治本。所以重启只能作为最后手段而且重启之前最好先确认一下my.cnf里的配置有没有问题不然大概率白折腾。另外我见过有人重启MySQL后连接数马上又满了原因是应用层的连接池配置了非常激进的初始连接数和最大连接数比如初始化200个连接最大连接数2000MySQL的max_connections才151那重启后不出一分钟就会再次被打满。这种场景下光重启没用必须同步修改应用配置。2.3 方法三用预留连接进入刚才提到MySQL会给root用户预留连接具体数量是多少其实预留的是max_connections 1也就是说连接数上限是100时系统实际上允许100个普通连接外加第101个连接专门给拥有SUPER权限的账号使用。你可以靠这个第101个连接进门但不代表你可以在里面为所欲为。很多人不知道SHOW PROCESSLIST这个操作本身也会占用一个连接。如果当前连接数已经到达max_connections 1的极限你连登录都会失败。这时候可以试试调整一下登录方式使用--skip-name-resolve跳过DNS反解析加快登录速度mysql -uroot -p --skip-name-resolve还有一个小技巧如果目标MySQL实例是可以通过TCP访问的但3306端口连接数满了可以在服务器本地上用socket登录。因为socket连接不算TCP连接其实算的同样是MySQL连接只是传输层协议不同。真正能绕过限制的还是只有那个额外的管理连接。3. 彻底解决调整参数并优化应用侧连接管理3.1 调整max_connections的正确姿势急救结束后就要正式面对问题了。假设你的业务高峰期确实需要800个连接那么就把max_connections从151上调到合理值。改之前先问自己三个问题当前你的MySQL实际峰值连接数是多少当前系统能支撑多少个连接应用层连接池是否合理第一个问题可以通过MySQL的统计信息来看SHOW STATUS LIKE Max_used_connections;这个变量记录的是自MySQL启动以来同时使用的最大连接数。如果这个值长期徘徊在max_connections附近说明参数确实不够如果实际最多才用到50那说明打满可能是连接泄漏而不是并发太高。第二个问题需要看你机器配置。一个简单的经验公式是max_connections innodb_buffer_pool_size / 单个连接内存开销。但实际很难精准计算因为连接内存开销受查询复杂度影响。我一般建议在2核4G的小机器上max_connections不要超过3004核8G的机器可以5008核16G的机器800到1000更大的机器按资源余量逐步增加不要盲目设几千否则一次高峰连接数上来直接吃满内存。改参数的位置在/etc/my.cnf一般是在[mysqld]段下[mysqld] max_connections 800改完后重启MySQL。但要注意如果连接数需求超过1000还需要同步调整操作系统的限制否则改完等于白改。3.2 别忽略wait_timeout和interactive_timeout很多人调大了max_connections就觉得万事大吉结果过了几天发现连接数又顶到新上限了。为什么因为连接被应用建立后即使不干活也不断开一直处于Sleep状态。MySQL有个参数wait_timeout控制非交互式连接的空闲超时时间默认是28800秒也就是8小时。如果应用层连接池配置不当或者代码里忘了释放连接这些空连接会一直挂着占着茅坑不拉屎。所以解决连接数打满问题一定要配合调整这两个参数[mysqld] wait_timeout 300 interactive_timeout 300interactive_timeout针对的是通过mysql客户端交互式登录的连接wait_timeout针对的是程序通过驱动建立的连接。建议把两个都设成300秒也就是5分钟。如果业务知道自己的连接空闲时间普遍很短可以设成更小的值比如120。但不要太小否则连接频繁被断开应用层又没做自动重连反而会出现Lost connection的报错。调整这两个参数的效果是空闲连接会被MySQL主动回收连接数自然降下来。这比单纯加大max_connections更健康因为连接数限制的目的是防止资源耗尽而不是让你无限容纳垃圾连接。3.3 应用侧连接池配置怎么改应用层的连接池配置往往比MySQL参数更重要。以Java常见的HikariCP为例你会看到这样的配置spring: datasource: hikari: minimum-idle: 10 maximum-pool-size: 50 connection-timeout: 30000 idle-timeout: 600000如果你的MySQLmax_connections是800而你有10个应用节点每个节点连接池最大50那理论最大就是500是安全的。但如果你有20个节点每个节点最大100那上限就是2000必然打满800。所以调MySQL参数的时候必须把应用节点数、连接池大小乘一遍确认不会超过max_connections。我见过一个案例应用代码里每次查询都新建连接用完不关导致连接数在几分钟内从20冲到500。解决办法很简单——改用连接池限制最大连接数并且测试空闲连接是否正确回收。这是治本。4. 实战排查定位连接打满的元凶4.1 查看当前连接状态连接数打满不一定是真正的并发需求大很可能是某个环节出了问题。为了搞清楚到底是什么占着连接执行下面这条SQLSELECT user, host, db, command, count(*) FROM information_schema.processlist GROUP BY user, host, db, command ORDER BY count(*) DESC;看结果时重点关注command列。如果是大量Sleep说明有连接空闲不释放如果是大量Query说明在并发执行查询需要进一步看是否出现了慢查询或锁等待如果某个host特别集中可能是有个应用节点在疯狂建连接。还可以看具体的连接来源SELECT host, user, count(*) FROM information_schema.processlist GROUP BY host, user ORDER BY count(*) DESC;通过这个结果你能快速判断是哪个IP、哪个用户在耗尽连接数。如果发现是一个从来没见过的IP在疯狂连接那就要考虑是不是被扫描了需要检查端口是否暴露在公网必要时用防火墙限制来源IP。4.2 常见元凶与对应解法元凶一慢查询堆积一条慢查询执行10秒期间连接一直占用。如果同时来了10个这样的查询10个连接就没了。如果是100个100个连接就没了。排查方式是看SHOW PROCESSLIST里Time列特别大的连接对应的SQL是不是走了全表扫描或者没命中索引。解决思路是优化SQL、加索引或者考虑读写分离分摊压力。元凶二连接池配置不当下线扩容刚才说了应用节点多的场景下一旦业务流量升高连接池自动扩展连接很容易把数据库连接数打满。这种情况的解决思路一是限制应用层最大连接数宁可让应用层排队等待也不要让数据库崩溃二是调大数据库max_connections但要配合系统资源一起评估。元凶三僵尸连接Java应用里获取连接后业务代码抛异常finally块忘记关闭连接。这种情况连接池里的连接会一直处于Aborted或Sleep状态。排查方式是查看SHOW GLOBAL STATUS LIKE Threads_connected;和SHOW GLOBAL STATUS LIKE Aborted_connects;如果Aborted_connects持续增长说明有不少连接中途断开需要检查网络和应用代码。元凶四MySQL自身内部线程问题还有一种少见的情况MySQL内部线程状态异常比如Table_locks_waited过高导致连接一直等待锁。这时即使连接数没到上限也会出现请求堆积间接打满连接数。解决思路是找出锁等待的事务用SHOW ENGINE INNODB STATUS;查看或者用SELECT * FROM information_schema.innodb_trx;定位事务。5. CentOS环境下的运维建议与避坑记录5.1 系统层面配置ulimit和limits.conf在CentOS上调整MySQL连接数必须同步调整系统限制。打开/etc/security/limits.conf添加mysql soft nofile 65535 mysql hard nofile 65535这里mysql是MySQL运行用户nofile是文件描述符数。注意ulimit -n对普通进程生效的是软限制如果MySQL是通过mysqld_safe或systemd启动的还要确认systemd服务文件里是否自定义了LimitNOFILE。我遇到过的情况是改了limits.conf重启MySQL然后再启动时报Failed to set locale但nofile没生效最后发现是mysqld.service文件里有单独的配置。检查生效值的方法cat /proc/$(pidof mysqld)/limits | grep open files如果显示的是65535说明生效了如果是1024说明没生效继续排查systemd配置。另外CentOS 7默认SELinux是enabled如果MySQL的连接数突然受限制可以临时检查SELinux是否拦截了连接但不建议直接关闭SELinux。可以用setsebool -P httpd_can_network_connect_db 1之类的操作但这里主要说MySQL就不展开了。5.2 监控与告警避免下次再被打懵解决完一次问题不代表就完了。我建议在服务器上写个简单的脚本定时监控MySQL连接数使用率超过阈值就往钉钉或者邮件发告警。脚本可以很简单比如#!/bin/bash MYSQL_USERroot MYSQL_PASS你的密码 MAX_CONN$(mysql -u$MYSQL_USER -p$MYSQL_PASS -Nse show variables like max_connections | awk {print $2}) CUR_CONN$(mysql -u$MYSQL_USER -p$MYSQL_PASS -Nse show status like Threads_connected | awk {print $2}) USAGE$(echo scale2; $CUR_CONN / $MAX_CONN * 100 | bc) if [ $(echo $USAGE 80 | bc) -eq 1 ]; then echo MySQL连接数使用率已超过80%当前$CUR_CONN / $MAX_CONN fi这种脚本虽然简陋但非常实用。更专业的做法是接入Prometheus mysqld_exporter Grafana不过那需要额外搭建一套监控体系。对小团队来说先用脚本顶着就够了。5.3 我踩过的几个坑实战中的血泪经验第一个坑改了max_connections重启MySQL发现启动失败。查看日志才发现是my.cnf里写错了段名把max_connections写在了[mysqld_safe]下面导致没生效。所以改完配置最好用mysqld --verbose --help | grep max_connections验证一下当前生效值。第二个坑杀空闲连接时一条KILL命令下去误杀了应用正在跑的连接。因为应用连接在SHOW PROCESSLIST里也会显示Sleep如果业务代码里有“连接空闲但后面还要用它”的逻辑比如长连接杀掉后就会导致该连接后续执行SQL时报错。所以杀连接前建议先确认time值只杀超过设定阈值比如300秒的空闲连接同时提醒应用侧做连接重连。第三个坑连接数打满后想通过mysql -uroot -p -h127.0.0.1连接但由于DNS反解析失败连接建立特别慢等了半天进不去。后来加上--skip-name-resolve才顺利登录。实际上如果MySQL开启了skip_name_resolveTCP连接就不做反解析了连接建立速度会快很多还能减少被恶意IP拖垮的风险。第四个坑CentOS 8及以上版本的MySQL默认是8.0max_connections参数名没变但一些老教程里的open_files_limit还要配上。别只改一项改完max_connections后建议也在[mysqld]下加一句open_files_limit 65535这个参数控制MySQL打开文件的总数和连接数直接相关。如果不设置MySQL会基于max_connections * 5之类的估算但可能不够用。6. 结语连接数打满这件事核心是连接生命周期管理最后想说的其实已经隐含在前面各部分里了Too many connections 很少是单纯的“参数不够大”而是连接的生产和回收失衡。你调大max_connections只是扩大了缓冲池但如果应用层无限创建连接、创建了不归还、归还了不释放再大的缓冲池也会被填满。比较健康的状态是MySQL的max_connections略高于应用理论峰值留20%余量空闲连接超时设置合理应用侧连接池有明确的maximum-pool-size和空闲回收策略同时监控盯紧张超阈值能提前告警。我自己的习惯是每接手一套新的MySQL服务先建一张小表记录max_connections和max_used_connections的历史快照每周看一次趋势。这样能提前发现连接数正在逐步攀升的隐患而不是等爆了再去救火。另外如果公司有自己的发布系统尽量在代码提交检查里加上“数据库连接是否显式关闭”的静态扫描规则。很多连接数打满的隐患其实从代码阶段就能避免。这篇文章里的操作命令和配置方式基本都能在CentOS 7/8/9上直接套用MySQL 5.7和8.0也都适用。具体的参数数值需要大家结合自己机器的内存、CPU和业务并发来微调切忌照抄。如果还有其他场景性问题欢迎在评论区一起交流。