1. 先说结论:max_connections是谁定的,默认值又是多少
这个问题几乎每个MySQL使用者都会遇到,尤其在做压测、高峰期排查、或者面试被问到的时候。但绝大多数人只记得一个模糊的答案:默认好像是151。准确地说,这个值由参数max_connections控制,MySQL 5.7和8.0的默认值确实是151。
为什么是151而不是别的数字?这个数值和MySQL内部线程模型有关,源自MySQL源码里的默认设定,具体来说,max_connections的默认值在源码中定义的时候就选了一个兼顾内存占用和并发能力的数字。151在大多数场景下够用,但也就是“够用”,你真要跑一个稍微像样的业务系统,这个数基本都要调大。
另外有个细节容易被忽略:如果你用的是MySQL 5.6或更早的版本,默认值只有100。所以看到老系统连接数限制特别低,先看看版本再下结论。
1.1 查一下你手上的MySQL:三行SQL看清当前状态
在讨论怎么改之前,先把查看当前状态的几个SQL记牢,后面排查问题全是靠它们。
sql复制-- 查看当前配置的最大连接数
SHOW VARIABLES LIKE 'max_connections';
-- 查看当前正在使用的连接数
SHOW STATUS LIKE 'Threads_connected';
-- 查看历史上达到过的最大连接峰值
SHOW STATUS LIKE 'Max_used_connections';
这三个字段的含义要区分清楚:max_connections是你设定的上限,Threads_connected是此刻还挂着的连接数量,Max_used_connections是自从MySQL启动以来,连接数最高冲到过多少。最值得关注的是Max_used_connections,如果它已经接近甚至等于max_connections,说明你的连接池不够用,迟早要出事。
顺便说一句,Max_used_connections记录的是MySQL进程生命周期内的峰值,重启后归零。所以如果MySQL跑了半年没重启,这个值比一天的压测结果更有说服力,它代表的是长周期内的真实压力上限。
1.2 为什么默认值偏偏是151
网上对151这个数字的解释众说纷纭,有人说是为了减少系统调用压力,有人说是经验值。从我查过的官方文档和源码来看,这个值更多是早期硬件条件下的一个保守选择。MySQL每创建一个连接,就要分配一个线程来处理,线程栈默认大小在Linux上是2MB(8.0里这个值也有调整空间),再加上各种buffer,一个连接轻轻松松吃掉几MB内存。如果默认值设得太高,在内存只有几百MB的机器上,MySQL可能连启动都困难。
所以151不是一个性能调优的结果,而是一个让MySQL在所有机器上都能跑起来的“安全值”。理解了这一点,你就明白为什么要根据自己机器的实际情况去调,而不是网上看别人设了5000自己也跟着设。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 真正限制连接数的,不只是MySQL自己
很多人以为把max_connections调大就万事大吉,结果改完重启MySQL一看,日志里报错,或者连接数还没到上限系统就卡死。这里要坦白一个事实:MySQL的最大连接数从来不是一个参数说了算,它至少被三层因素同时制约,参数只是最表面的那一层。
2.1 操作系统文件描述符:第一道隐形瓶颈
Linux下“一切皆文件”,网络连接也是文件,每个TCP连接至少要占用一个文件描述符(file descriptor)。MySQL进程能打开多少文件描述符,由ulimit -n决定。最尴尬的场景就是你辛辛苦苦把max_connections改成2000,结果系统的open_files_limit还是1024,连接数刚过1000就直接报错。
MySQL实际上有个自我保护机制:它启动的时候会检测open_files_limit、max_connections等几个参数,然后自动调整。但自动调整的逻辑比较复杂,我的建议是手动设置,别依赖它的默认行为。在my.cnf里加一行:
ini复制open_files_limit = 65535
这个值的计算有一个经验公式:max_connections * 5左右,因为MySQL每个连接除了socket文件描述符,还可能额外打开临时文件、表文件等。比如你计划把max_connections设为10000,那open_files_limit至少给50000,留出余量才不会被瓶颈卡住。
2.2 线程与内存:每个连接背后都是真金白银
MySQL内部对每个连接的处理方式是“一连接一线程”,这个和有的数据库用事件驱动模型不太一样。也就是说,1000个连接就是1000个线程。每个线程默认的栈大小在Linux上是2MB,但这不代表每个连接就只占2MB。
还要算上这些:
thread_stack:默认256KB或更大,线程执行时的栈空间sort_buffer_size:每个连接做排序时分配的缓冲区,默认256KBjoin_buffer_size:连接执行join时使用的缓冲区,默认256KBread_buffer_size、read_rnd_buffer_size:默认各256KB
这些buffer很多都是按需分配的,连接创建时不会立刻全部分配,但一旦并发执行复杂查询,每个连接可能轻松吃满几MB。所以一台内存只有8GB的机器,你配置max_connections=2000,乐观估计每个活跃连接占5MB,峰值时光连接缓冲区就可能占10GB,系统直接OOM。这就是为什么光调参数不评估硬件,等于挖坑给自己跳。
2.3 排查要绕开的坑:那些“看似连接数不够”的假象
有一种非常常见的情况:Threads_connected不高,Max_used_connections也没到上限,但应用就是报“Too many connections”。这时候先别急着调大max_connections,先看另一个状态:Threads_running。
sql复制SHOW STATUS LIKE 'Threads_running';
Threads_running是指当前正在执行语句的线程数。如果你发现这个值长期很高,说明你的连接并没有“闲”着,而是每条SQL都在慢查询里煎熬。这种情况下,连接数只是一个表象,真正的病根是慢SQL把线程都占满了,后面的请求排队排队再排队,连接自然就被占满了。
另一种假象是连接泄漏。应用代码里获取了连接但不释放,Threads_connected一直涨,涨到上限就爆。这种问题调大max_connections只是治标,找到哪个应用连接不释放才是关键。
3. 修改最大连接数的完整实操流程
前面讲完原理,这里给一套可以直接照抄的配置方法。注意,我不建议你只改一个参数就完事,下面这几个步骤要一起做,才能真正把连接数提上去。
3.1 修改max_connections的正确姿势
生产环境推荐改配置文件,不要用SET GLOBAL临时改,因为重启后失效。在my.cnf的[mysqld]段落里:
ini复制[mysqld]
max_connections = 2000
max_connect_errors = 1000000
max_connect_errors这个参数要顺带说一下。默认值是100,它的含义是:如果客户端连接失败次数累计超过100,MySQL会把这个客户端的IP锁死,之后所有连接请求直接拒绝。在你调整连接数的过程中,很容易因为反复测试连接失败触发这个限制。调大它或者干脆把它设成一个很大的数,能少踩一个坑。
改完配置后需要重启MySQL才生效:
bash复制# systemd管理的系统
systemctl restart mysqld
# 或者用service命令
service mysql restart
重启前建议先执行mysqladmin ping确认当前实例状态,并挑一个业务低峰期操作。很多人忽略了这个顺序,结果配置还没改完,生产环境直接给你脸色看。
3.2 同步调整操作系统层的限制
光改MySQL配置还不行,Linux系统对进程能打开的文件数有硬限制,得同时调整。
先看当前限制:
bash复制ulimit -n
如果小于你的目标连接数,就需要修改。以systemd管理MySQL为例,在/etc/systemd/system/mysqld.service.d/limits.conf里加上:
ini复制[Service]
LimitNOFILE=65535
改完执行systemctl daemon-reload,再重启MySQL。如果你用的是源码方式部署、直接跑mysqld_safe,那要在/etc/security/limits.conf里给启动用户加限制:
code复制mysql soft nofile 65535
mysql hard nofile 65535
一个容易忽略的检查点:修改完这些系统限制后,回到MySQL里确认一下实际生效值:
sql复制SHOW VARIABLES LIKE 'open_files_limit';
如果这个值还是1024之类的小数字,说明你的系统配置没生效,MySQL可能是在启动过程中读取了错误的限制。
3.3 验证修改是否生效
改完以后,不要只看SHOW VARIABLES,还要实际压一下连接数。用sysbench可以模拟大量连接:
bash复制sysbench --test=oltp --mysql-host=127.0.0.1 --mysql-port=3306 \
--mysql-user=your_user --mysql-password=your_password \
--mysql-db=test --max-requests=0 --num-threads=2000 \
--max-time=60 run
跑完看Threads_connected有没有达到你预期的值。如果压测到一半直接“Too many connections”,说明还有一层限制没打通,回到2.1和3.2继续排查。
4. 连接数爆满之后:真实场景的排查思路
连接数爆满的报错“ERROR 1040: Too many connections”,是所有DBA和开发最不想看到的报错之一。但真正的问题往往不是“连接数不够”这么简单,而是一连串事件链的最终爆发点。我复盘几个典型场景。
4.1 一个典型的“连接数耗尽”现场
假设你的系统突然收到大量报警,应用日志里全是Too many connections的报错。此时的正确操作顺序是这样的:
第一步,先保住MySQL能连上。你可能会发现:连接数满了,DBA自己也连不上去了。这时候预留一个管理通道就非常重要,在my.cnf里可以启用:
ini复制[mysqld]
skip-networking=0
extra_port=33060
extra_port是MySQL 5.7.8之后引入的管理端口,单独给DBA留的,即使主端口连接数已满,你依然可以通过这个端口连进去执行KILL操作。这个参数平时看起来没用,关键时刻能救命。
第二步,连进去后立刻执行:
sql复制-- 查看当前所有连接的状态和来源
SELECT user, host, db, command, time, state, info
FROM information_schema.processlist
ORDER BY time DESC;
重点关注三列:command(Sleep说明是空闲连接,Query说明正在执行)、time(执行时间)、info(具体SQL)。如果大量连接的command是Sleep且time很大,基本可以断定是连接泄漏;如果大量Query且time都很大,那多半是有慢查询拖垮了整个实例。
4.2 连接数的分布:谁是罪魁祸首
当连接数量异常高时,光看processlist还不够,要做一次聚合分析,快速找到“元凶IP”和“元凶账号”:
sql复制-- 按来源IP分组统计连接数
SELECT SUBSTRING_INDEX(host, ':', 1) AS ip, COUNT(*) AS cnt
FROM information_schema.processlist
GROUP BY ip
ORDER BY cnt DESC
LIMIT 20;
-- 按账号分组统计
SELECT user, COUNT(*) AS cnt
FROM information_schema.processlist
GROUP BY user
ORDER BY cnt DESC;
这一步能快速定位是哪个应用、哪个IP连接数异常高。基于我的经验,连接数异常往往集中在某一个业务应用上,比如定时任务写了个循环重试的逻辑,或者某个服务进程挂掉后不断重连。锁定了来源,后面的处理就简单了。
如果确认是某个具体连接在跑慢SQL,可以直接干掉这个会话:
sql复制KILL 12345;
注意不要把正常连接杀掉,先看一眼time和info确认是不是该死的那条。
4.3 临时救火与长期治理
临时处理手段有两个:一是把不健康的连接kill掉,腾出连接名额;二是临时调大连接数上限:
sql复制SET GLOBAL max_connections = 3000;
但要注意,这种临时方案只能让你多喘一口气,如果机器的内存和CPU已经吃紧,调大连接数反而可能加速系统崩溃。所以临时手段只是过渡,长期治理才是重点。
长期治理的核心是两件事:一是解决连接泄漏,让连接数回归正常水位;二是引入连接池,让应用复用的是有限的连接而不是无限创建新连接。在Java生态里,HikariCP是默认推荐;Go语言里database/sql自带连接池,配置好SetMaxOpenConns和SetMaxIdleConns就行。连接池的本质是把“应用到数据库”的流量整形成固定的几条管道,避免数据库端连接数被无谓地打满。
5. 连接数设置在多少才合理:算一笔资源账
这是一个很难一句话回答的问题,因为没有放之四海而皆准的数字。但从资源角度可以做精确的计算。
5.1 从内存反推:一个连接吃掉多少
上面提到的buffer都按“最坏情况”估算:假设每个连接都在跑复杂的排序和join,一个连接的内存占用约2MB到5MB。做预算时按5MB算,留足余量。
公式很简单:
可用内存 / 单连接预估内存 = 理论最大连接数
假设你的MySQL专用机器内存是32GB,操作系统和MySQL基础进程占用大约4GB,剩余约28GB可分配给连接。按5MB一个连接算:28GB除以5MB约等于5600个连接。但实际经验告诉我,别把内存用满,留出20%给操作系统page cache,否则查询的数据没法在内存里缓存,性能骤降。按这个标准,实际建议配置在4000到4500之间。
这只是内存维度。如果CPU核数很少,比如4核,那Threads_running能同时跑的就那么几个,你再怎么把连接数调高,SQL执行还是排队。连接数和CPU一样需要匹配,否则会出现“连接很多但都闲着、跑着的都很慢”的诡异现象。
5.2 从业务反推:QPS、RT与并发
从业务侧算更实际。假设你的系统平均响应时间(RT)是50ms,单条连接每秒最多执行约20次请求(1000ms除以50ms)。如果业务需要支撑2000 QPS,最简单粗暴的估算:
需要并发连接数 = 2000 / 20 = 100
但这只是理论值,实际会有波动,峰值可能是均值的2到3倍。所以给连接池设置200到300就足够了。这里的结论可能出乎很多人意料:大多数业务系统需要的根本不是几千个连接,而是几百个。
MySQL官方也一直强调:连接数不是越多越好。连接太多,线程切换的开销变大,单个查询的响应时间反而恶化。有一种经典现象叫“连接风暴”:你不断调大连接数上限,结果每条查询都变慢,每个连接都占着资源不放,然后应用因为超时重试又创建更多连接,形成恶性循环。
5.3 架构层的最终解法:连接池与Proxy
当单机连接数实在不够用,或者不希望应用直连数据库,架构上通常做两件事:
第一,引入代理层,比如ProxySQL、MySQL Router或Vitess。代理层统一管理数据库连接,应用只和代理建立连接,代理再和后端数据库维持一个相对固定的连接池。这样即使有成百上千个应用实例,数据库端看到的连接数也是可控的。
第二,合理的连接池大小设置。这里有个被广泛引用的经验公式:
连接池大小 = ((核心数 * 2) + 有效磁盘数)
但这个公式适合IO密集型应用。Web应用常见的做法是设置成10到30之间。一个4核8线程的Java应用,HikariCP连接池设成20基本能应对绝大多数场景。除非你的数据库查询特别慢,否则调大连接池只是让更多请求排队。
我在压测中实测过一个项目:同样的数据库,连接池从50调到200,QPS不仅没涨,反而因为数据库端线程争抢明显下降。这不是理论推论,是实打实的性能测试结果。所以,调大连接数之前,先想想“我需要这么多连接吗”,而不是“我能不能开这么多连接”。
6. 常见问题速查:连接相关的典型报错与处理
这部分整理成表格,方便排查时快速对照。
| 报错或现象 | 根本原因 | 处理方式 |
|---|---|---|
| ERROR 1040: Too many connections | max_connections已达上限 | 检查连接来源,kill空闲连接,评估是否调大上限,必须配合内存检查 |
| 连接数未满但应用报连接失败 | 可能是max_connect_errors触发了IP锁定 | 调大max_connect_errors,或者FLUSH HOSTS |
| MySQL启动后open_files_limit仍是1024 | 系统ulimit限制没生效 | 修改systemd的LimitNOFILE或limits.conf后重启 |
| Threads_connected不高但系统卡死 | Threads_running很高,慢查询堆积 | 定位慢SQL,优化索引或改写SQL,而不是加连接数 |
| 客户端连接成功但执行SQL报错丢失连接 | wait_timeout或interactive_timeout过短 | 适当调大这两个参数,检查连接池的闲置回收策略 |
| MySQL重启后连接数配置失效 | 之前用SET GLOBAL修改,没写入配置文件 | 在my.cnf中持久化配置 |
6.1 连接池相关的经典错误配置
这里单独说一个最常见的问题:应用层连接池的最大连接数设置得比数据库max_connections还大。比如数据库最大连接数是150,但应用的HikariCP配置了maximumPoolSize=200,一个应用实例就能把数据库连接吃光。如果还有多个应用实例,数据库就更不可能扛得住了。
一个合理的配置原则是:所有应用实例的connection-pool上限之和,要小于数据库max_connections的70%,留出30%给运维操作和突发查询。比如数据库设1000,那5个应用实例每个上限最多140左右。这样才能保证数据库连接永远有富余,DBA也始终能连上去执行诊断。
6.2 几个容易踩的细节
wait_timeout和interactive_timeout这两个参数经常被忽视。默认是28800秒,也就是8小时。如果应用层连接池没有及时回收闲置连接,MySQL这边等了8小时后主动断开,应用再用这个连接时会报“Connection lost”。报错信息不一定是Too many connections,而是Communications link failure或者Connection reset。处理方式是检查应用连接池的idleTimeout和maxLifetime设置,确保它们小于MySQL的wait_timeout,这样连接是由连接池主动回收,而不是被MySQL暴力断开。
另外,连接数的监控也不能只看一个时间点。我习惯写一个简单的巡检脚本,把Threads_connected、Max_used_connections、Threads_running这三项定时采集下来,画成趋势图。这样能观察到连接数的变化趋势,判断是缓慢泄漏还是业务波峰,不至于每次都等“爆了”才去救火。
7. 分享一个我压测MySQL连接数时的真实案例
之前帮一个客户排查数据库频繁“Too many connections”的问题,客户的MySQL配置了max_connections=2000,内存64GB,听起来很充裕。但奇怪的是,在线用户才几百人,连接数却经常冲到2000。
第一次排查,我发现大量连接来自同一个IP,而且command全是Sleep。顺着IP找到了一个Java后端服务,看了代码发现问题很典型:一个定时任务里通过DriverManager.getConnection()获取连接,用完只关闭了Statement和ResultSet,唯独没有关闭Connection。每个任务跑一轮就泄漏一个连接,任务每5分钟跑一次,一天下来泄漏了288个连接,三天就把数据库连爆了。
这个案例说明一个道理:连接数上限只是一个“安全垫”,真正要管住的是应用层的连接生命周期。修好代码后,这个系统的连接数稳定在200左右,max_connections我也没有调回去,保留2000作为应对突发流量的缓冲。因为有余量,即使某些时刻连接数冲到500,也完全不用紧张。
所以当你再看到“MySQL最多能有多少连接”这个问题,正确的回答不是报一个具体数字,而是反问:你的机器内存多大?应用层的连接池怎么配的?有没有连接泄漏?慢查询控制住了没有?把这些问题回答好,连接数的上限自然就有了答案。
