都2025年了,居然还有人问我“MySQL最多能有多少连接”。说真的,这个问题在社区里隔三差五就会出现一次,但每次回答完我都觉得,提问的人可能只想知道一个数字,却完全没搞明白数字背后那条“连接链”是怎么一步步断掉的。今天我不打算只甩一个配置项给你,而是想顺着“连接数”这条线,把最大连接数、并发瓶颈、线程模型、连接池参数这些掰开揉碎讲一遍。你照着这篇文章去排查,大概率能定位到你系统真正的卡点在哪。
先说结论:如果单看MySQL自身的配置,max_connections的最大值上限在MySQL 5.7及8.0里通常是100000(10万),但实际上绝大多数业务服务器根本扛不到这个数。真是场景里,300到1000才是更常见的合理区间。而且连接数只是“你能开多少个门”,真正让你死机的是“开门后同时有多少人涌进来办事”,也就是并发查询数量。搞混这两个概念,排查方向从一开始就是歪的。
1. 连接数的本质:一条TCP链路在MySQL内部经历了什么
1.1 每次“连接”不仅是MySQL的事
很多人理解“MySQL连接数”就是数据库开几个会话窗口,其实不完整。MySQL的连接是标准的TCP连接,从客户端发起connect()开始,整条链路上至少要经过:
- 客户端socket创建与TCP三次握手
- MySQL线程接收连接,分配
thread_id,初始化会话上下文 - 校验用户名密码和主机白名单
- 执行
max_connections计数检查,如果超过上限直接报Too many connections
这里有个隐性问题,大部分新手压根没意识到:“连不上MySQL”大概率不是MySQL拒绝了你,而是操作系统先拒绝了TCP连接。我见过太多事故现场,max_connections明明还有富余,但应用就是报Can't connect,最后查出来是/etc/security/limits.conf里的nofile文件描述符限制太小,或者tcp_max_syn_backlog溢出,把连接请求堵在了内核层面。
所以讨论“最多能有多少连接”,实际上要同时看三层:
- MySQL自身的
max_connections - 操作系统层面对进程fd数量和TCP连接队列的限制
- 物理内存是否支撑得起这么多线程和会话缓存
1.2 max_connections到底能设多大
max_connections是一个动态变量,也就是说在运行中可以直接SET GLOBAL修改,不需要重启实例。默认值在MySQL 5.7为151,8.0也保持了这个传统。这个数值显然太小,因为它面向的是比较保守的默认场景。
你可以通过以下SQL查看当前值:
SHOW VARIABLES LIKE 'max_connections'; SHOW STATUS LIKE 'Threads_connected'; SHOW STATUS LIKE 'Max_used_connections';Max_used_connections记录的是实例启动以来达到过的最大连接数峰值,这个比Threads_connected更能反映真实水位。很多运维只看当前连接数,结果白天业务高峰已经冲到2000了,他还在那说”我们的连接数才几十“,那当然是被瞬时波动掩盖了。
把max_connections调到10万有没有意义?我直接说:对99%的项目没有意义。MySQL每开一个连接,就要创建一个线程(connection-per-thread模型),每个线程默认thread_stack是256KB,再加上会话级内存,几百个线程同时活跃的时候内存和上下文切换开销就已经很可观了。你用10万连接数做配置,内存可能直接被打爆。
1.3 别把“连接数”当并发能力
这里必须强调一个经典误区:连接数不等于并发能力。并发能力是指同一时刻有多少请求正在执行SQL并占用CPU/磁盘IO,连接数只是会话的数量。很多连接建立后处于Sleep状态,啥活没干,就是占着一个坑位。
一个600连接的库,如果其中550个都在Sleep,真实并发可能只有50。这时候你把max_connections翻倍大概率解决不了性能问题,反而让线程堆积得更严重。真正要看的是Threads_running,这个状态值代表正在执行语句的线程数。如果Threads_running居高不下,连接数再怎么调也是白搭。
2. 查问题先查链路:一个“连接数爆了”的真实定位过程
2.1 从一次线上事故说起
去年有个客户找我,说业务高峰期MySQL直接报Too many connections,应用批量失败。他当时的max_connections是500,已经觉得不小了。我先让他做了一件事:
mysql -u root -p -e "SHOW STATUS LIKE 'Threads%';" mysql -u root -p -e "SHOW VARIABLES LIKE 'max_connections';"结果很有意思,Threads_connected卡在450左右,Threads_running却只有20多。这说明问题根本不是连接数硬件上限触顶,而是大量连接滞留不断累积,导致坑位被占满。
接着查了SHOW FULL PROCESSLIST,发现大量Sleep状态的连接来自同一个业务IP,KeepAlive参数没配好,连接处于假死状态。客户端以为连接还活着,实际上服务端早就断了,但断开的连接没有及时被清理,于是不断新建连接、再滞留、再新建,形成一个恶性的连接泄漏循环。
2.2 逐步排查连接泄漏的有用脚本
排查连接问题,我最常用的三连操作:
-- 1. 按用户分组看连接占用 SELECT user, host, db, COUNT(*) AS cnt FROM information_schema.processlist GROUP BY user, host, db ORDER BY cnt DESC; -- 2. 按来源IP分组看连接分布 SELECT SUBSTRING_INDEX(host, ':', 1) AS client_ip, COUNT(*) AS cnt FROM information_schema.processlist GROUP BY client_ip ORDER BY cnt DESC;通过这个脚本,很快就能看到哪个应用、哪个IP在疯狂占连接。很多情况下不是数据库的问题,而是应用层的连接池配置有漏洞。
2.3 临时跟永久调整的连接数
如果线上已经告警,可以应急调大:
SET GLOBAL max_connections = 1000;但要特别注意,这个操作只对后续新连接生效,已在排队等待的请求依然会失败。另外,SET GLOBAL修改的是运行时值,重启MySQL之后会重新回到配置文件里的值。想要永久生效,还得改my.cnf或my.ini里的max_connections项,然后重启。
这里多提一句:千万不要在大促前临时调这个还不通知开发。我曾经见过有人把max_connections从500调到5000,结果业务方连接池的maximumPoolSize也跟着乱跳,瞬间五千个线程抢锁,数据库没崩,应用先崩了个稀里哗啦。
3. 连接数相关参数的正确打开方式
3.1 不只是max_connections,这些参数连坐
连接问题从来不是单一参数能解决的,和它关联的参数还包括:
| 参数名 | 作用 | 典型设置 |
|---|---|---|
max_connections | 最大连接数上限 | 300~1000 |
max_user_connections | 限制单个用户最大连接数 | 按应用隔离 |
wait_timeout | 非交互连接超时秒数 | 60~120 |
interactive_timeout | 交互式连接超时秒数 | 120~300 |
thread_cache_size | 线程缓存数量 | 与连接峰值相关 |
back_log | 连接请求排队数 | 128~512 |
max_execution_time | 慢查询熔断时间(8.0) | 按需设置 |
wait_timeout特别值得说。默认值在MySQL 8.0是28800秒,也就是8小时。如果应用侧没有合适的连接回收机制,大量空转Sleep连接能在这个时间里一直占着坑。我一般建议明确设置成60秒甚至更短,配合应用层连接池的validationInterval去自动剔除失效连接。
为什么要让数据库主动断连接而不是让应用池来管?因为很多时候应用框架的线程模型并不可控。比如某些老PHP应用,每个请求都会直连MySQL,又没有连接池概念,请求结束如果不显式关闭,连接就滞留在MySQL端。这时候wait_timeout就是最后一道安全生产线。
3.2 通过系统状态判断你到底缺不缺连接
不看SHOW STATUS就调参数,等于蒙眼开车。我建议每次调优前先记录这几项:
-- 历史达峰连接数 SHOW GLOBAL STATUS LIKE 'Max_used_connections'; -- 当前连接数 SHOW GLOBAL STATUS LIKE 'Threads_connected'; -- 正在运行的线程数(真实并发) SHOW GLOBAL STATUS LIKE 'Threads_running'; -- 历史被拒绝的连接次数 SHOW GLOBAL STATUS LIKE 'Connection_errors_max_connections';Connection_errors_max_connections只要不是0,就说明曾经发生过因为连接数上限而拒绝连接的情况,哪怕影响时间只有几十毫秒。这个指标很隐蔽,但它是“已经碰过天花板”的铁证。
如果Max_used_connections长期低于max_connections的70%,而业务还老是卡顿,问题多半不在连接数上,要去查SQL效率、锁等待、IO瓶颈。相反,如果你发现Max_used_connections已经非常接近max_connections,但同时Threads_running很低,那就说明连接池配置过于激进,应用创建了太多闲置连接,该收的是应用侧。
4. 连接池不是“越大越好”:Druid、HikariCP与MySQL的爱恨情仇
4.1 HikariCP为什么默认只有10
很多Java开发者第一次看到HikariCP的默认配置都会愣一下:maximumPoolSize默认10?这也太小了吧。我连接数都调到1000了,连接池才10个连接,岂不是浪费?
这里得讲清楚一个底层权衡。连接池里的连接是复用的,每个池化连接在一次SQL执行完毕之后并不关闭,而是归还池中。10个连接如果每个查询都秒级返回,理论上每秒钟能处理远超10个请求,因为请求是排队轮流使用这10个连接。
但如果你把连接池调到200,而数据库max_connections只有500,再有三个应用实例同时连这个库,每个实例200就是600,直接突破数据库上限。这种“连接池叠加效应”是分布式架构下最常见的连接数失控原因。
4.2 连接池参数要跟上max_connections的节奏
我见过的最佳实践是:数据库max_connections按“所有客户端连接池上限之和再乘以1.5冗余”来设计。比如你有3个Java服务实例,每个连接池maximumPoolSize=100,那么数据库至少预留450到600的连接上限。同时再监控一下认证连接数,不能让某个实例把池子撑爆。
Druid作为国内使用率很高的连接池,配置上有一个关键参数容易被忽略:
spring.datasource.druid.test-while-idle=true spring.datasource.druid.validation-query=SELECT 1开启这个之后,Druid会定期检查空闲连接是否可用,如果MySQL那边因为wait_timeout把底层连接断掉了,连接池能及时发现并剔除,应用就不会拿到一个表面正常实际已经坏掉的连接。
我之前排查过一个诡异问题:应用启动没问题,但只要放置一晚上,第二天早高峰第一次请求必报Communications link failure,重启应用就好。典型原因就是连接池里的连接被MySQL断了,但连接池不知道,继续分发不可用连接。针对这种情况,除了开启空闲检测,还可以把连接池的connectionTimeout调低,配合MySQL的wait_timeout双向掐断死连接。
4.3 连接池参数速查表
这里给一份我实践中验证过比较稳的连接池基线配置,基于Spring Boot 2.x + HikariCP:
spring.datasource.hikari.minimumIdle=5 spring.datasource.hikari.maximumPoolSize=50 spring.datasource.hikari.connectionTimeout=30000 spring.datasource.hikari.connectionTestQuery=SELECT 1 spring.datasource.hikari.idleTimeout=60000 spring.datasource.hikari.maxLifetimeTime=1800000 spring.datasource.hikari.validationTimeout=5000注意maxLifetimeTime应该小于MySQL的wait_timeout,比如MySQL的wait_timeout是8小时,那maxLifetimeTime控制在30分钟以内比较稳妥,留出足够余量避免池内连接被数据库单方面回收。
5. 直连MySQL还是走中间件:连接数的分岔路
5.1 单机直连的场景边界
如果你只是一个小项目,数据库单实例,应用不超过三五个,直连MySQL完全没问题。此时max_connections设个300到500就够用了。关键是应用层的连接池必须控制好,别每个服务各自为政往大了调。
5.2 引入Proxy后的连接收敛
流量一大、实例一多,直连模式就会暴露出两个问题:连接数不可控、故障切换困难。这时候就轮到中间件出场了,比如ProxySQL、MyCat或者云厂商的数据库代理。中间件统一收口数据库连接,应用只连中间件,数据库连接数不再跟实例数量相乘增长,而是被中间件按需分配。
我之前一个客户,五十多个微服务实例直连MySQL,每个实例默认连接池20,理论上最大并发连接数就是1000,数据库直接跪了。后来把应用全部切到ProxySQL,数据库max_connections其实没怎么变,但中间件把真实连接收敛到200左右,因为很多服务其实用不了那么多并发连接,收口后效果立竿见影。
5.3 用缓存扛连接风暴
再拓展一步,连接数爆炸的时候,很多时候根本不该去连数据库。接口热点数据如果打到Redis上就能扛住,连接数压力自然就降下来了。我有个原则,同一个热点数据,能缓存就缓存,MySQL连接是稀缺资源,不该让每个请求都去消耗一次往返。
6. 连接数问题排查的常见坑和速查表
6.1 报错信息到底在说什么
连接类报错最容易让人迷惑,这里整理几个最常见的:
| 报错信息 | 真正含义 | 初步动作 |
|---|---|---|
Too many connections | 连接数达到max_connections上限 | 查Threads_connected、Max_used_connections,紧急调大或杀Sleep |
Can't connect to MySQL server on 'x.x.x.x' (10060) | 网络不通或防火墙拦截 | 检查防火墙、安全组、网络连通性 |
Can't connect to MySQL server on 'x.x.x.x' (10061) | 端口被拒绝或mysqld未启动 | 确认MySQL进程监听状态 |
Communications link failure | 连接被远端中断 | 排查连接池、wait_timeout、网络链路 |
ERROR 1129 (HY000): Host is blocked | 连接失败次数过多被临时封禁 | 清空host_cache并挂载防火墙策略 |
特别说下Host is blocked,这个问题出现的概率不高但很致命:如果你的某台客户端机器连接失败次数连续超过max_connect_errors(默认100),MySQL会在内存里把这个主机标记为阻塞,之后所有来自该IP的连接都会被直接拒绝。解决办法是FLUSH HOSTS清缓存,然后去应用侧排查为什么老是连失败。
6.2 那Sleep连接怎么清理
很多同学看到一堆Sleep连接就开始杀,这是不对的。Sleep连接本身无害,真正有害的是连接池或长连接机制没有正确复用它们,导致连接无限堆积。你可以做以下动作:
- 如果确认是某个无用的会话,直接
KILL <id>; - 如果Sleep连接数量持续异常,先查应用侧的连接池回收逻辑;
- 如果
wait_timeout设置得过大,调小它让MySQL自动清理。
我的经验是先问一句:这些Sleep连接从哪里来,为什么新连接不断增加而不是复用?如果这个问题不解决,你杀掉的连接马上会被新建连接补回来,治标不治本。
6.3 一张图理清连接数排查顺序
这不是流程图,但我习惯按这个顺序排查连接问题:
- 看
Max_used_connections有没有触顶 - 看
Threads_connected当前水位 - 看
Threads_running真实并发 - 查
PROCESSLIST找异常来源 - 检查连接池是否正常回收
- 确认OS文件描述符限制和TCP队列没有成为瓶颈
这六步做完,基本能把90%的假“连接数爆炸”排除掉,剩下的才轮到真正需要动max_connections参数的场景。
7. 实操案例:把max_connections从200平稳调到2000
7.1 场景还原
客户的一个电商系统,平时并发不高,但每逢活动就会瞬时涌入大量连接。因为活动前的压测报告显示连接峰值500左右,所以客户当时把max_connections设成了1000,想着足够冗余了。结果活动开始半小时后开始报Too many connections,连接数进度瞬间顶满。
我先查了Threads_connected和Threads_running,发现连接数涨到900的时候,Threads_running才70。由此判定是连接建立过快、连接池回收不及时,而非真实并发太高。当时在活动期间,我建议的处理是两步走:
-- 第一步,将等待空闲时间调低,让Sleep连接快速被回收 SET GLOBAL wait_timeout=60; SET GLOBAL interactive_timeout=120; -- 第二步,临时调大连接数上限,扛过峰值 SET GLOBAL max_connections=2000;7.2 为什么先调wait_timeout而不是直接调max_connections
因为活动期间的连接数暴涨大部分是客户端在大量新建连接,但连接执行完SQL后不及时关闭。wait_timeout缩短后,MySQL可以更快地把这些空连接断掉,从而腾出坑位。直接调max_connections虽然也能立竿见影允许更多连接进来,但会让物理资源被大量Sleep连接白白占住,后续其他查询性能反而会下降。
活动结束后我把配置写回了my.cnf:
[mysqld] max_connections = 1000 wait_timeout = 60 interactive_timeout = 120 thread_cache_size = 128然后重启MySQL让配置永久生效。重启前我特意保留了重启前的状态指标,重启后对比发现,Max_used_connections在下一轮活动中稳定在600到700,而Threads_running依然保持在100以内,说明连接质量明显变高了。
7.3 案例复盘,我想说的话
这个案例的环境不算复杂,但很典型。要我总结的话,连接数问题的本质不是“调大就能扛”,而是“杜绝无意义连接占用资源”。连接池、超时时间、应用生命周期管理和MySQL参数,必须四管齐下,缺一环都容易踩坑。
8. 最后再分享两个小技巧
第一,监控脚本里一定要加一项Threads_connected / max_connections的比率告警,我通常设置在80%预警、90%触发紧急告警。这个指标比单纯看QPS更能提前暴露连接池耗尽风险。
第二,如果你用MySQL 8.0,可以试试把max_execution_time配到查询级别,给每条SQL设定一个最大执行时间。这样即使连接数没有爆,每条SQL也不会无限占着线程资源不出活,变相提高了连接利用率。
MySQL连接数这个问题,表面上一个配置项就能回答,实际上牵涉到操作系统、网络、应用框架和数据库线程模型的完整链路。下次再有人问“最多能有多少连接”,你可以告诉他:官方上限10万,但真正决定你系统活不活得下来的,是你怎么规划连接的使用方式。