聊到 MySQL,很多人第一时间想到的是 SQL 优化、索引选择、事务隔离级别这些老生常谈。但作为一个天天跟数据库打交道的从业者,我被问到最频繁的一个问题其实是:"MySQL 最多能有多少连接?" 这问题听起来特别简单,但每次回答我都要想几秒钟,因为它就不是一个该背数字的问题,而是一个典型的资源规划问题:连接数背后牵扯到操作系统的文件描述符、线程栈内存、InnoDB 缓冲区、应用层连接池的回收时机,甚至还有 DNS 解析的速度。
如果你直接去搜,会看到各种答案:151、100000、几万、受文件句柄限制……这些说法都没错,但都只说了一半。真正让这个数字变成"生产事故"的,往往不是上限本身,而是你没搞清楚自己的机器到底能撑多少、哪些环节在悄悄消耗连接数。这篇文章我不打算给你一个"标准答案",而是把这个问题拆开揉碎:连接数上限由谁决定、实际能扛多少由什么决定、生产环境如何规划、连接数打满之后怎么救火。看完你就能给自己的 MySQL 算出一笔明白账。
1. 连接数表面上是数字,实际上是一本"资源账本"
先说一个最容易被忽略的事实:MySQL 处理连接的方式,不是像 Nginx 那样用事件驱动、少量进程扛海量并发,而是"一个连接对应一个线程",这个模型业内叫 one-thread-per-connection。也就是说,你每建立一条连接,MySQL 就要创建一个线程来伺候它。线程不是免费的,它要占内存栈、占 CPU 调度资源、占各种缓冲区的名额。
1.1 一条连接到底吃掉了哪些资源?
一条 MySQL 连接占用的资源,粗略拆开大概是这些东西:
- 线程栈(thread_stack),默认 256KB,这是固定分配的,每个线程一个。
- 网络缓冲区(net_buffer_length),默认 16KB,负责读写协议数据。
- 各种查询相关的缓冲区:sort_buffer_size 默认 256KB,join_buffer_size 默认 256KB,read_buffer_size 默认 128KB。这些不是连接建立时就分配的,而是查询执行到排序、join、顺序读等特定阶段才按需分配。
- 至少两个文件描述符:一个给 socket 连接,一个给当前打开的表或文件。
- 线程状态变量、字符集转换 buffer、权限校验产生的临时数据等。
注意,这里有个非常重要的差异:一个空闲连接和一个正在跑大排序的连接,内存占用完全是两个量级。空闲连接在 Linux 上大概占 1~3MB 内存(主要是线程栈和协议缓冲区),但一个正在执行几十万行排序的连接,sort_buffer 再加上临时表,可能瞬间吃下几百 MB。这就是为什么很多 DBA 会发现"连接数看着不多,数据库内存却突然飙升"——因为真正耗内存的不是连接本身,而是连接上挂着的查询。
1.2 max_connections 只是第一道闸门
max_connections 这个参数,是 MySQL 允许同时建立的连接数量上限。默认值是 151,5.7 和 8.0 都是这个数,官方之所以给得这么保守,是因为每连接的内存开销摆在那里,默认配置必须保证普通服务器也能跑得起来。
这个参数的可配置上限是 100000,注意,这只是"MySQL 配置层面允许的最大值",不是"你的机器允许的真实上限"。真要调到十万,那得先问问操作系统和内存答不答应,这个话题后面会展开。
另外有一个很少有人提到的机制:当连接数真正打满 max_connections 之后,普通账号连接会直接报 ERROR 1040 (HY000): Too many connections,但具有 SUPER 权限(MySQL 8.0 里是 CONNECTION_ADMIN 权限)的管理员账号仍然有一个"紧急连接通道",这是 MySQL 故意留的活口,方便 DBA 在连接打满时还能登录进去救火。所以生产环境务必留一个权限足够的管理员账号,别在关键时刻把自己锁在门外。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 什么在真正限制你的连接数?
既然 max_connections 理论上能调到 100000,为什么没人真的这么干?因为还没等 MySQL 拒绝你,操作系统和硬件就先崩了。连接数的真实上限,由三个层面共同决定。
2.1 操作系统的文件描述符是第一个天花板
在 Linux 下,每个 TCP 连接都是一个 socket,socket 本质上就是一个文件描述符(fd)。MySQL 进程能打开多少个文件描述符,受进程级 ulimit -n 限制,也受系统全局的 fs.file-max 限制。
如果进程的 ulimit -n 只有 1024,那 max_connections 就算设成 10000 也白搭。因为每个连接至少要占一个 fd,而 MySQL 还要打开表文件、binlog、redo log、undo log,这些全都算在同一个进程的 fd 配额里。所以实际经验是,open_files_limit 至少要给到 max_connections 的 2 倍以上,连接数与文件操作才不会互相挤兑。
查看当前进程的 fd 限制:
bash复制ulimit -n
cat /proc/$(pidof mysqld)/limits | grep -i "open files"
在 MySQL 里也可以直接查配置值:
sql复制SHOW VARIABLES LIKE 'open_files_limit';
我在一台新服务器上部署 MySQL 时,第一件事就是先确认 ulimit,再动 max_connections,顺序反了后面容易踩坑。
2.2 内存是第二道,也是最容易爆的坎
每个连接一个线程,每个线程有栈,每个连接有网络缓冲区,几千个连接堆在一起,直接就是几个 GB 的内存。更麻烦的是,MySQL 的 InnoDB buffer pool 通常要占系统内存的 50%~70%,这部分是给数据页缓存的,不能省。两者叠加,连接数开得太大,数据库的整体内存会非常紧张,严重时操作系统直接触发 OOM Killer,把 mysqld 整个进程杀掉。
我规划 max_connections 时的粗算思路是这样的:
- 先看系统总内存,减去 InnoDB buffer pool 和操作系统保留内存(大约 20%~30%),剩下的就是可以分给连接的量。
- 每个活跃连接按 3~5MB 估算,空闲连接按 1~2MB 估算。如果业务以短查询为主,可以取低值;如果经常有大的排序、join、临时表,必须取高值。
- 算出来的数字再打八折,才是生产环境可以接受的 max_connections。
举个例子,一台 64GB 内存的数据库,InnoDB buffer pool 给 32GB,OS 保留 16GB,剩 16GB 分给连接。每个连接平均按 4MB 算,理论能撑 4000 个连接,打八折就是 3200。但这只是内存维度,还得看下一节的 CPU 调度。
2.3 线程调度和锁竞争是看不见的隐形杀手
当连接数到了几千这个量级,CPU 花在线程上下文切换上的时间会显著增加。这时候你会看到一个很典型的现象:CPU 的 sys(内核态)占用率非常高,user 态(真正执行 SQL)反而不高。这就是连接过多导致线程调度开销反噬了数据库性能。
这种情况跟 SQL 写得好不好没有任何关系,你优化索引、改写 SQL 都没用,因为瓶颈已经不在 SQL 执行层面,而在并发调度层面。MySQL 官方也知道这个问题,所以企业版和 Percona Server 提供了 Thread Pool 功能,把大量连接映射到少量工作线程上,就是为了缓解这种调度压力。这个后面会单独说。
3. 查看、调整与规划连接数的正确方式
前面讲了原理,这部分给干货,都是可以立刻拿去用的命令和操作。
3.1 几条查看连接状态的命令
最常用的五条:
sql复制SHOW VARIABLES LIKE 'max_connections';
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW GLOBAL STATUS LIKE 'Max_used_connections';
SHOW GLOBAL STATUS LIKE 'Connection_errors_max_connections';
SHOW PROCESSLIST;
这几个指标要区分清楚:
| 指标 | 含义 | 用途 |
|---|---|---|
| max_connections | 配置的连接上限 | 评估容量 |
| Threads_connected | 当前活跃的连接数 | 实时监控 |
| Max_used_connections | 自启动以来的峰值连接数 | 判断历史水位 |
| Connection_errors_max_connections | 因连接数打满而失败的次数 | 判断是否发生过打满 |
| PROCESSLIST | 每个连接的来源、状态、正在执行的 SQL | 排查问题第一入口 |
我建议每台数据库都配上监控,重点盯 Max_used_connections / max_connections 这个比例。如果长期超过 70%,就要准备扩容或者优化连接使用方式了,别等打满再处理。
3.2 修改 max_connections 的正确姿势
临时生效(重启后失效):
sql复制SET GLOBAL max_connections = 1000;
永久生效:修改配置文件 /etc/my.cnf,在 [mysqld] 段下加一行:
ini复制[mysqld]
max_connections = 1000
然后重启 MySQL。注意,这儿的"正确姿势"不只是改数据库,还得同时检查 open_files_limit 是否够大。如果只改了 max_connections 而没管文件描述符,很可能调大后不仅没生效,反而因为 fd 不足引发其他怪问题。
还要注意,SET GLOBAL max_connections 只对新连接生效,已经建立的连接不受影响。所以在连接打满的时候,执行这个命令不一定能立刻缓解,因为已有的连接还占着位置,通常要配合 kill 空闲连接一起做。
3.3 连接数规划的经验公式
结合我自己的经验,一个务实的规划方式:
- 常规业务库:300~500 就够用。
- 高并发、有连接池且连接复用良好的库:800~1500。
- 超过 2000 就要非常慎重,除非用了 Thread Pool 且硬件足够。
关键公式在应用侧:所有应用实例的连接池上限之和,最多只能占数据库 max_connections 的 60%~70%。留出来的 30% 以上空间,是给 DBA 手工查询、报表任务、数据备份、临时排查用的。我见过太多事故,就是开发把所有连接池加起来正好等于数据库连接上限,结果一个后台批量任务跑起来,数据库直接拒绝正常业务连接。
4. 连接数问题的实战排查
这一节是真正的重点,因为大多数读者遇到"连接数"问题,都不是配置规划阶段,而是已经出了故障在救火。
4.1 遇到 Too many connections 怎么处理
经典的报错长这样:
code复制ERROR 1040 (HY000): Too many connections
处理顺序很重要,别上来就盲目杀连接和改配置,先分三步走:
第一步,先看都有哪些连接占着位置,连了多久,来自哪些 IP:
sql复制SHOW PROCESSLIST;
SELECT id, user, host, db, command, time, state, info
FROM information_schema.processlist
ORDER BY time DESC;
第二步,把空闲时间特别长、明显是"占着茅坑不拉屎"的连接杀掉。比如 keepalive 挂掉后残留的死连接、应用连接池忘记释放的僵尸连接,或者直接执行:
sql复制KILL <thread_id>;
第三步,如果连上来后发现还在持续增长,那就要看是不是真的流量峰值,还是连接泄露。连接泄露是最恶心的场景,通常表现为 Threads_connected 只涨不跌,时间越长越接近 max_connections。
4.2 连接被"吃光"的几种隐蔽原因
我在实际维护中遇到过好几类"不知不觉把连接数耗尽"的情况,都在常规文档里很难查到,列出来供参考。
-
wait_timeout 设置过大。默认 wait_timeout 是 28800 秒(8 小时),应用连接池拿到连接后如果空闲超过这个时间,MySQL 才会主动断开。如果应用连接池又设了很长的空闲保活时间,几百个连接就会一直挂在数据库上,日积月累把连接数吃光。我一般建议把 wait_timeout 调到 300 秒左右,配合应用连接池的空闲回收策略一起用。
-
应用连接池配置了超大连接数。比如某个应用部署了 50 个实例,每个实例连接池 maximumPoolSize 配了 200,那就是 10000 个潜在连接。数据库再大也扛不住。正确做法是:单实例连接池一般 20~50 足够常规业务用,除非是批量处理场景,否则别贪大。连接池不是越大越快,太大了反而增加数据库端的锁竞争和调度开销。
-
DNS 反查拖慢连接认证。如果
skip_name_resolve参数是 OFF(默认),MySQL 会对每个新连接做反向 DNS 解析。DNS 一旦慢,连接就会长时间停留在"认证中"状态,占着连接名额但啥也没干。高并发下这一秒的积压就能打满连接数。对公网/跨网段访问的场景,建议直接在配置里加上skip_name_resolve=1,风险是授权表里 host 字段不能再写域名,必须写 IP,这个要提前规划好。 -
连接数其实没打满,但 TCP backlog 满了。瞬时并发连接太多时,新的连接可能排队在操作系统层。back_log 参数控制这个队列长度,如果太小,即使 MySQL 还有连接配额,新连接也会在 socket 层被拒绝,应用层看到的现象同样是"连不上"。
4.3 应用连接池参数要和数据库对齐
应用连接池(HikariCP、Druid、Tomcat JDBC Pool)是数据库连接的第一道防线,它的参数配置直接决定了数据库看到的连接形态。
我常用的几个配合思路:
- maximumPoolSize:单实例不超过 50,结合数据库 max_connections 除以连接池实例数来计算。
- minimumIdle:不要设置成和 maximumPoolSize 一样大,否则应用一启动就占满全部连接。一般设置为 5~10。
- connectionTimeout:建议 3000ms~5000ms,太快容易误报,太慢会让应用请求堆积。
- maxLifetime:建议比数据库 wait_timeout 短 30 秒以上,确保连接在数据库主动断开前被应用回收。比如数据库 wait_timeout=300,连接池 maxLifetime 就设 240 秒左右。
- idleTimeout:空闲回收时间,建议设在 60 秒左右,把不活跃的连接早点放回池里。
这套参数配合好了,连接池就像节流阀,能把数据库连接数稳定在一个安全水位。否则连接池就会变成泄洪闸,一个流量峰值就把数据库冲垮。
5. 连接数到底开多大?边界场景和踩坑心得
讲完排查,最后聊聊边界场景,以及我踩过的一些坑。
5.1 把连接数调得特别大,会怎么样?
有人觉得"既然连接数越大越好,我直接调到 5000、10000 行不行"?我劝你冷静。连接数调大,本质上是用内存和 CPU 调度资源换并发能力,但这种交换的代价是逐渐递增的。
当连接数超过某个阈值后,增量连接对系统吞吐的贡献几乎为零,反而拖垮整体性能。我有一次测试,同一台机器,连接数从 500 加到 2000,QPS 不但没涨,反而从 1.5 万掉到 1.2 万,CPU 的 sys 占比从 8% 飙到 22%,大量时间都耗在线程上下文切换上了。
所以,连接数不是配置得越高越安全,而是要找到你硬件条件下的"甜点区"。
5.2 Thread Pool 到底需不需要?
很多同学一听到高连接数就想到 Thread Pool。确实,Oracle MySQL 企业版、Percona Server、MariaDB 都提供了 Thread Pool 插件,核心原理是让有限的 worker 线程复用执行大量连接的查询,而不是一个连接一个线程。
但 Thread Pool 不是银弹。我自己的判断标准是:如果连接数通常在 2000 以下,且应用层连接池规范,根本不需要 Thread Pool,它的调度逻辑还会增加单条查询的延迟。如果连接数长期在 2000 以上,尤其是大量短连接场景(比如频繁建立和断开连接),Thread Pool 的帮助会非常明显。
实际生产环境,我更倾向于先解决"连接为什么这么多"这个问题,比如优化连接池、合并实例、拆分数据库,而不是一上来就上 Thread Pool。这属于架构层面的最后手段,不是第一选择。
5.3 一次真实的连接数故障复盘
前两年我参与维护过一套业务系统,数据库配置的 max_connections 是 400,应用连接池加起来也正好 400,平时跑得很欢。某天早晨流量高峰期,数据库突然报 Too many connections,应用侧大面积超时,研发第一反应就是把连接池上限调大,结果越调越严重。
我们上去看 processlist,发现大量连接集中在一条"大 SQL"上。这条 SQL 是个六表 join 的报表查询,单次执行就要跑 30 秒,平时几乎没有并发,但那天有运营同时跑了十几个批任务,所有连接瞬间都被这条大 SQL 占住,其他业务的正常查询全被饿死。
排查顺序是先杀掉这批慢查询,数据库恢复正常,然后把 max_connections 从 400 调到 600,同时给所有连接池总和设了 70% 的上限阈值。最后把这条大 SQL 拆成了多个小查询,用临时表分批处理,彻底根治了。
这个案例给我最大的教训是:连接数满了,问题往往不在连接数本身,而在连接上挂的 SQL 有多差。 连接数只是表象,慢 SQL 和连接泄露才是真凶。排查的时候,第一件事永远是看 processlist 里连接在干什么,而不是急着调参。
最后再分享一个实用的小技巧:日常巡检时,除了看连接数的绝对值,更要看 Connection_errors_max_connections 这个计数。如果这个值从某一天开始持续增长,说明系统已经在连接数上限边缘试探了。这时候再去查连接来源、优化连接池配置,把隐患处理在爆发之前,远比等它打满后救火来得轻松。
