授权与合规声明
本文全部操作对象均为自建隔离靶场(本机容器或隔离虚拟机),涉及安全测试的环节必须以取得合法授权为前提。未经授权的渗透测试违反《中华人民共和国网络安全法》与《刑法》相关条款,须承担相应法律责任。本文只讲环境配置、版本对照与靶场隔离,不含任何攻击步骤、利用载荷与绕过手法,请勿将文中环境指向任何非自有系统。
一、“连不上”要先分成两类
1.1 同一个现象,两条不同的因果链
靶场里最常见的一幕:某个页面突然连不上库。你去重启服务,好了;过一会儿又不行。或者把数据库重启一遍,好了;半小时后又是老样子。
“连不上”这三个字,其实盖住了两类完全不同的事。
第一类是路不通。客户端发出去的这次连接,压根没走到数据库跟前——地址不对、端口上没人监听、中间有东西挡着、认证没通过。这一类的特征是谁连都一样:你的网页连不上,命令行大概也连不上。
第二类是名额满。路是通的,数据库也确实在跑,但这次连接被挡回来了,因为它手里没有空位置。这一类的特征正好相反:有人连得上,有人连不上——而且往往是少数账号连得上,应用连不上。
区分这两类不用翻日志,先问一句:同一时刻,换一个客户端、换一个账号,能不能连上?能,就往第二类走;不能,先查第一类。
1.2 本篇只处理第二类
第二类的完整链条有三段:上限是多少 → 谁在占 → 占的那些该不该占。
三段里最容易搞错的是第一段。多数人记住的是“上限是一个配置项,默认 151”,然后把它当成唯一答案。手册不是这么说的。这个上限会被另一层资源压住,实际生效的可能是另一个数——第二章展开。
第二段是“谁在占”。这里有一个反直觉的点:占名额的往往不是“正在查询的请求”,而是已经查完、但连接没还回去的那些。一句话,占着名额的人不一定在干活。
第三段是判断:这些占着的连接,是设计如此,还是漏了。
1.3 本篇不写什么
先把边界划清楚,免得带着错误期待往下看。
不写“把上限调大”这件事。手册给了这个变量的取值范围,本文只讲它怎么生效、被什么压住,不给任何建议值。理由很实在:上限该多大,取决于这台机器上还跑着什么,这个数不是抄来的。
不写客户端那一侧怎么选路径、怎么发起连接。那属于另一篇的范围,本篇从“这次连接已经到了数据库跟前、被挡下来”这一刻开始讲。
也不写数据搬迁这类操作层面的话题。它与连接名额没有因果关系,混进来只会让排查线变乱。
读法建议:第二章看上限由什么决定,第三章看那个多出来的名额,第四章看一个连接占什么,第五章看应用侧怎么把名额用掉,第六章看怎么数,第七章是自查顺序。急着排障可以直接跳第七章。
二、上限是一个数,但不是唯一的数
2.1 手册给的那个默认值
MySQL 8.0 手册在系统变量那一节(截至 2026-10-20 的 8.0 版页面)给max_connections列了四行属性:
| 属性 | 手册取值 |
|---|---|
| Scope | Global |
| Dynamic | Yes |
| Type | Integer |
| Default Value | 151 |
说明原文第一句是:
The maximum permitted number of simultaneous client connections.
simultaneous这个词要圈出来。它管的不是“一共来过多少个连接”,是“同一时刻能同时存在多少个”。这两个数在观感上差得很远:一个页面一天被开很多次,只要每次开完就关,同时存在的可能一直很少;反过来,一个页面只被打开过几次,但每次都留着不关,同时存在的就会一直堆着。
Dynamic: Yes这一行也值得看一眼。它的意思是这个值不需要重启就能改。这一条本身不是建议,但它解释了一个常见困惑:为什么有人“改完没重启也生效了”,也有人“改了没生效”——前者动的是运行时的值,后者动的是配置文件里的字,两者不是同一层。
2.2 实际生效值:两个数取小的那个
紧接着的一句是本文最该背下来的:
The maximum effective value is the lesser of the effective value of open_files_limit - 810, and the value actually set for max_connections.
拆开看,它说了三件事。
第一,存在“effective value”(生效值)这个概念。你设的那个数,不一定是最后生效的那个数。手册在这里把“设了什么”和“生效了什么”分成了两句话,这个区分是整篇的地基。
第二,生效值由两项比较得出:一项是open_files_limit的有效值减去 810,另一项是max_connections的设定值。取较小的那个。
第三,注意open_files_limit前面也带着“effective value”——它自己也可能不是配置文件里写的那个数。所以这是一条两级的链:文件句柄那一层先算出自己的有效值,减掉 810,再去和连接数的设定值比。
由这段原文可以直接推出一个结论(这是推导,不是原句):只把max_connections往上调,可能一点用都没有。如果文件句柄那一层给不出那么多,取较小者会把你的调整整个吃掉。表现就是“我明明改了,怎么还是那个数”。
2.3 为什么中间要减掉一个常数
手册没有解释 810 这个数是怎么来的,本文也不猜。
能说的只有一句:数据库自己也要用文件句柄——它要打开数据文件、日志、临时文件,还有别的运行时资源。所以“能拿出多少给客户端连接”不是open_files_limit的全额,而是扣掉内部开销之后的余量。
这解释了一个容易忽略的事实:连接数上限不是一个可以在真空里调的数。它挂在一个更底层的资源账上。这也是同一份配置搬到另一台机器上表现可能不一样的原因——那台机器文件句柄这一层给的额度不同。
要看这一层,得分别读两个值:设定的连接数,和文件句柄那一层的实际额度。下面两条都是只读查询:
⚠️代码待验证
mysql-e"SHOW VARIABLES LIKE 'max_connections';"mysql-e"SHOW VARIABLES LIKE 'open_files_limit';"读法:第一个数出来的是当前生效值,不是配置文件里的字;第二个也是有效值。两个都拿到,才能按 2.2 那句原文比一次。
如果两个数都对不上你记忆里的配置,先别怀疑自己记错——配置文件可能根本就没被读到。同一台机器上可能躺着不止一份配置文件,而服务实际读的是哪一份,由启动时的方式决定。要确认这一点,可以看服务自己报出来的默认值来源:
⚠️代码待验证
my_print_defaults mysqld这条命令打印的是“按当前环境会被采用的配置项”,比直接翻文件更接近真相。至于它在你机器上输出什么,以输出为准,本文只给写法。
三、那个多出来的名额
3.1 原文:实际上允许 +1
手册的故障排查附录 B.3.2.5 节(截至 2026-10-20 的 8.0 版页面)在讲这个问题时,第一句先给判断:
If clients encounter [此处为手册所述连接耗尽的报错名,按要求不引] when attempting to connect to the mysqld server, all available connections are in use by other clients.
方括号里的部分,手册原本写的是一个具体的报错名称,本文按规定不写报错文本,用方括号代替。意思不变:所有可用连接都被其他客户端占着。
紧接着的一段才是本节的核心,逐字为:
mysqld actually permits max_connections + 1 client connections. The extra connection is reserved for use by accounts that have the CONNECTION_ADMIN privilege (or the deprecated SUPER privilege).
三处细节值得停下来看。
actually permits——手册用“实际上允许”这个词,是在纠正一个想当然:你以为上限就是max_connections,实际允许的是它加一。
reserved for use by accounts that have the CONNECTION_ADMIN privilege——这一个名额不是留给“先到的人”,而是绑在权限上的。没有这个权限的账号,即使抢在最前面,也用不上它。
or the deprecated SUPER privilege——SUPER是旧写法,手册明确标了 deprecated(已弃用),但仍然认。所以在判断“我这个账号有没有这个名额”时,不能只看CONNECTION_ADMIN,还要看有没有那个旧的。
3.2 这个设计解决的是什么问题
同一段后面还给了一句用途说明,逐字为:
By granting the privilege to administrators and not to normal users (who should not need it), an administrator can connect to the server and use SHOW PROCESSLIST to diagnose problems even if the maximum number of unprivileged clients are connected.
这句话把设计意图讲得很清楚:名额满的时候,得有人能进去看。如果满了就谁都进不去,这个系统就没法诊断了——你连“谁在占”都看不到。
所以这个 +1 不是“预留的缓冲”,而是一条诊断通道。它默认只对管理员开放,普通账号拿不到,这是刻意的。
由这段可以推出一条判读(推导):你能连上,不代表没满。如果你是管理员账号,满的时候你照样进得去,进去以后一切正常,于是很容易得出“数据库没问题”的结论——而外面那些应用正在一个个被挡回来。这是本篇最想纠正的误判。
3.3 反过来:应用连不上,你连得上
把 3.1 和 3.2 合起来,得到一个很实用的判据:
| 现象 | 可能的位置 |
|---|---|
| 管理员账号能连、应用账号连不上,且管理员进去能看到一堆连接 | 名额被占满,应用账号没有那个额外名额 |
| 管理员账号也连不上 | 不是名额问题,回到第一章的第一类 |
| 两个账号都能连,但应用偶发失败 | 连接在“够用”和“不够用”之间来回,看第五章 |
第一行是本文的主战场。它同时说明了一件事:“我能登进去”这个证据,分量比想象中低——因为你登的可能正是那道专门给你留的门。
四、每个连接都要占一份资源
4.1 连接不是免费的
一个连接建立之后,数据库要为它准备一份运行时上下文:这个连接是谁、当前在哪个库、上一次执行到哪儿、会话级的变量是什么,以及一块用来放这次查询中间结果的地方。
这些东西跟着连接走,不跟着查询走。查询结束了,上下文还在,因为下一个查询可能马上就来,还要接着用。
(max_user_connections的默认值、thread_cache_size的具体影响、单个连接的内存开销究竟是多少,本次未逐字核到手册对应条目,一律标「待验证」,本文不给任何数值。这里只保留一个定性结论:每个连接都占一份资源,这份资源在连接关掉之前不会还回去。)
由此得到本章最重要的一句:“空闲”不等于“不占资源”。一个连接即使一条语句都不执行,它那份上下文也还在。
4.2 长连接把“瞬时占用”变成“常驻占用”
短连接的做法是:要用就连,用完就断。这样同时存在的连接数,大致等于“同一时刻正在处理的数量”。
长连接的做法是:连一次,一直留着。这样同时存在的连接数,大致等于“被打开过、还没断的那些”。
这两个数在业务低峰期差别不大,高峰期差别很大——但真正的麻烦不在高峰期,在高峰期过去之后。短连接会随着请求结束自然回落;长连接不会,它会一直停在那里把名额占着,直到超时或被回收。
这就解释了第一章那个“重启就好、过一会儿又不行”的现象:重启把名额清空了,但只要那些连接还会被重新建立、重新留住,一段时间后又是同一个局面。重启是清账,不是改账。
4.3 一个连接从生到死,账是怎么走的
按顺序看,一个连接会走过四段:
| 阶段 | 占着什么 | 谁决定它结束 |
|---|---|---|
| 建立 | 一个名额,加一份运行时上下文 | —— |
| 空闲 | 同上,一样不少 | 连接自己什么时候断 |
| 执行 | 上面那些,再加执行期的临时资源 | 语句什么时候执行完 |
| 关闭 | 逐步释放 | 客户端主动断,或服务端回收 |
表里最该看的是第二行。“空闲”这一行占的东西和“建立”那一行完全一样——手册没有给“空闲连接占用更少”这样的说法。所以“它没在跑查询,应该不占什么”这个直觉是错的。
第三行也值得注意:执行期的临时资源是叠加在连接那份之上的,不是替换。所以“连接数够”和“这台机器扛得住”是两个问题,不能互相代替。
五、应用侧:连接是复用还是新建
5.1 连接池做的事,是把“新建”换成“借还”
到这里,问题已经从“库的上限是多少”转到了“应用这一侧到底开了多少个连接”。
如果应用每次请求都新建一个连接、用完就关,那么它同时持有的连接数,大致跟着“同时处理的请求数”走。如果应用用连接池,那么它在启动或首次用到时先建一批,之后请求来了借一个、用完还回去。
(MySQL 客户端这一侧的连接复用机制、PHP 的mysqli/PDO持久连接行为、连接串里p:前缀的确切语义,本次未逐字核到手册条目,标「待验证」。本章只讲“复用会改变谁在占名额”这一层判断,不给具体写法。)
复用带来的第一个变化是:连接数不再跟着请求数走,而是跟着池子的配置走。请求再多,池子最多也就那么多;请求再少,池子里的连接也还挂在那儿。
听起来是好事——它把“上限”从“看运气”变成了“看配置”。但它同时带来一个新问题:池子的上限是应用自己定的,跟数据库那个上限没有任何联动。
5.2 两个上限之间没有自动对齐
这是本章要讲清的核心。数据库那边有一个上限(第二章那个取较小者的数),应用这边有一个池子的大小。这两个数之间不存在任何自动协商。
于是有三种局面:
| 局面 | 表现 |
|---|---|
| 池子总量小于数据库上限 | 名额用不满,也永远撞不到墙,同时没什么弹性 |
| 池子总量大致等于数据库上限 | 谁先到谁占,剩下的被挡;表现是“有时好有时坏” |
| 池子总量大于数据库上限 | 池子里的一部分连接注定建不起来 |
第三种最容易被忽略,因为它看起来配得很充裕。实际上多出来的那部分只会在真正需要的时候失败——平时看不出来,一忙就露。
算这笔账的方法:不是看某一个应用的池子多大,是看所有连这个库的东西加起来。同一台库上可能挂着好几种角色:Web 应用、定时任务、后台脚本、监控采集,还有你自己开着的那个命令行窗口。每一个都有自己的连接,每一个都在同一个池子里取名额。
这一步经常是排查的转折点:你以为只有应用在连,实际上还有别人。
5.3 “占着名额的人”可能是三种不同的东西
把第五章收一下。占名额的粗分三类,处理方式完全不同。
第一类,正在干活的。这类占着是应该的,问题不在它,在总量。
第二类,干完活没走的。长连接、池子里的空闲连接、忘了关的脚本。这类是“占着但不产出”,也是最常见的浪费。
第三类,卡住的。连接还在,但它的语句一直没结束——可能在等锁,可能在跑一个很久的查询。这类最麻烦,因为它既占名额,又在消耗执行资源,而且不会自己好。
三类的区分方法在第六章:看状态、看时长、看它在等什么。
配套资料:第五章这张“三种占名额的人”的分类,连同 2.2 那句原文的逐项拆解、第七章的五步自查顺序,我整理成了一页纸的排查卡,每个判断都标了本文的章节号,方便回头对依据。放在资料包里,扫码即可获取:
六、怎么数“现在有多少个”
6.1 三条只读的观察路径
数连接不需要任何写操作。三条路径,从粗到细。
第一条,数个数。看当前同时存在多少个连接,再和第二章那个生效上限比。
第二条,看明细。手册在 3.1 引用的那段里明确提到了SHOW PROCESSLIST——它是被写进“名额满时管理员该怎么办”这句话里的,说明这是官方指定的诊断入口。这一条比第一条多给了两个维度:每个连接来自谁、处在什么状态。
第三条,看配置。确认“我以为的上限”和“实际生效的上限”是不是同一个数。
⚠️代码待验证
mysql-e"SHOW PROCESSLIST;"mysql-e"SHOW VARIABLES LIKE 'max_connections';"mysql-e"SHOW VARIABLES LIKE 'open_files_limit';"三条都是只读。第一条给一张表(行数就是当前连接数),第二条给一个生效上限,第三条给文件句柄那一层的额度。这三个结果要一起看:只有个数、没有上限,判断不了“满没满”;只有上限、没有个数,判断不了“离满还有多远”。
不想连进去的话,也可以在系统层面数这一侧的网络连接。这条只是旁证,不是权威值:
⚠️代码待验证
ss-tnstate established'( sport = :3306 )'为什么说它是旁证:系统层面数到的是“这台机器上和这个端口有关的连接”,它可能把别的进程算进来,也可能漏掉一些状态。以数据库自己报的数为准,系统层面这个只用来交叉验证。
6.2 一个数说明不了什么,三个维度才行
拿到“当前连接数”之后,别急着下结论。同一个数,可能是完全不同的三件事:
| 维度 | 要看什么 | 说明了什么 |
|---|---|---|
| 来源 | 这些连接分别来自哪些客户端 | 是应用在占,还是别的角色在占 |
| 状态 | 是在执行、空闲,还是在等 | 是“该占的”,还是“占着不干活的” |
| 时长 | 每个连接存在了多久 | 是短连接的自然堆积,还是长连接的常驻 |
这三个维度合起来,才对得上第五章那三类。
只看来源,你会得出“是应用占的”,可应用占着也许是正常的。只看时长,你会得出“这些连接存在很久了”,可久不代表错——池子里的连接本来就该一直在。
判断“该不该占”,要的是三个维度的组合,不是其中任何一个单独的数。
6.3 从这里开始,问题才变得可回答
数清楚之后,原来那个“数据库为什么拒绝连接”就拆成了几个具体问题:
- 生效上限是多少?是被
max_connections卡的,还是被文件句柄那一层卡的? - 当前占了多少?离上限还差多少?
- 占着的里面有多少是空闲的?空闲了多久?
- 这些连接来自哪几个客户端?每一个客户端的池子多大?
四个问题都答得出来,“该动哪里”就不用猜了。
反过来说,这四个问题答不出来就去改配置,是在赌。而且赌输的概率不低——第二章那句原文已经说明,你改的那个数,可能压根不是生效的那个数。
七、自查顺序
7.1 五步,按顺序走
把全篇收成一条线:
第一步 同一时刻换个账号,能不能连上? ├─ 不能 ── 路不通,本篇到此为止(1.1) └─ 能 ──── 继续 第二步 你用来试的那个账号,有没有那个额外名额? ├─ 有 ──── 你走的是诊断通道,"能连上"不能证明没满(3.1 / 3.2) └─ 没有 ── 继续 第三步 生效上限是多少?被哪一层卡的? ├─ 是 max_connections 卡的 ── 继续 └─ 是文件句柄那一层卡的 ──── 先看那一层(2.2 / 2.3) 第四步 当前占了多少?来源、状态、时长分别是什么? (6.1 / 6.2) 第五步 占着的属于哪一类:干活的 / 没走的 / 卡住的? (5.3)这张图的顺序不能调。第一步没做就往下走,很可能在查一个根本不存在的名额问题;第二步没做就下结论,很可能把“我能进”当成“没满”。
7.2 三件可以带走的事
第一,上限不是“一个数”,是“两个数取小的那个”。手册原句写得很直白:生效值取open_files_limit有效值减 810 与设定值中较小的那个。记住这半句,能省掉很多“我明明改了”的困惑。
第二,“能连上”不是“没满”的证据。max_connections + 1那一个名额是留给带CONNECTION_ADMIN(或旧的SUPER)权限的账号的,用途就是让管理员在满的时候还能进去看。你走的可能正是那条通道。
第三,占名额的不一定在干活。空闲连接占的东西和刚建立时一样多,“没在跑查询”不等于“不占资源”;而长连接把这个占用从瞬时变成了常驻。
7.3 最后一句边界
本篇不给“该设成多少”的答案,这不是藏私——这个数取决于这台机器上还跑着什么,以及所有连它的应用各自开了多大的池子。第二章那句原文决定了:你调的那一格,可能根本不是生效的那一格。
能确定的是:在数清楚“谁在占”之前,任何关于上限的调整都是在猜。
本章可以带走的一句:先分清是路不通还是名额满,再去看是谁把名额用掉了。
配套资料:第七章这五步自查顺序,加上第二章的原文拆解和第五章的三类占位者,我做成了一张可以打印的排查卡,每一步都标了章节号,照着走不会跳步。放在资料包里,扫码即可获取:
附表 A:本文引用事实与官方出处对照表
| # | 事实(逐字或紧贴原文的转述) | 出处 | 核验日期 | 本文位置 |
|---|---|---|---|---|
| 1 | max_connections:Scope Global、Dynamic Yes、Type Integer、Default Value 151 | docs.oracle.com,MySQL 8.0 Reference Manual,系统变量页(DB1) | 2026-10-20 | 2.1 |
| 2 | “The maximum permitted number of simultaneous client connections.” | 同上(DB1,逐字;docs.oracle.com 系统变量页) | 2026-10-20 | 2.1 |
| 3 | “The maximum effective value is the lesser of the effective value of open_files_limit - 810, and the value actually set for max_connections.” | 同上(DB1,逐字;docs.oracle.com 系统变量页) | 2026-10-20 | 2.2 / 2.3 / 7.2 |
| 4 | “只调 max_connections 可能无效,因为生效值取较小者” | 由 DB1 原句推出(本文推导,非原句) | 2026-10-20 | 2.2 |
| 5 | 810 这个常数的来源,手册未作解释 | 手册未给说明,本文明确不作推测 | 2026-10-20 | 2.3 |
| 6 | 连接耗尽时手册的判断句:所有可用连接都被其他客户端占着(原文含报错名,按要求以方括号代替、不引报错文本) | docs.oracle.com,MySQL 8.0 Reference Manual 故障排查附录 B.3.2.5(DB2) | 2026-10-20 | 3.1 |
| 7 | “mysqld actually permits max_connections + 1 client connections. The extra connection is reserved for use by accounts that have the CONNECTION_ADMIN privilege (or the deprecated SUPER privilege).” | 同上(DB2,逐字;docs.oracle.com 附录 B.3.2.5) | 2026-10-20 | 3.1 / 7.2 |
| 8 | “By granting the privilege to administrators and not to normal users … use SHOW PROCESSLIST to diagnose problems …” | 同上(DB2,逐字;docs.oracle.com 附录 B.3.2.5) | 2026-10-20 | 3.2 / 6.1 |
| 9 | 每个连接都占一份运行时资源,空闲连接不释放这份资源(定性) | DB4:具体数值待验证,本次未逐字核到手册条目 | 2026-10-20 | 4.1 / 4.3 |
| 10 | 应用侧连接复用会改变“谁在占名额”的判断 | DB5:待验证,mysqli/PDO持久连接细节本次未逐字核到 | 2026-10-20 | 5.1 |
| 11 | 连接池大小与数据库上限之间无自动对齐,需按全部客户端合计 | 由 DB1 的“同时存在”口径推出(本文推导) | 2026-10-20 | 5.2 |
| 12 | SHOW PROCESSLIST是手册指定的诊断入口 | 同第 8 条(DB2) | 2026-10-20 | 6.1 |
| 13 | 本文全部命令均未在本机运行(本机无 mysql / mysqld),一律标「代码待验证」 | 本机环境说明 | 2026-10-20 | 2.3 / 6.1 |
附表 B:术语速查表
| 术语 | 一句话解释 |
|---|---|
max_connections | 允许同时存在的客户端连接数,全局、动态、整数,手册默认值 151 |
| 生效值(effective value) | 手册用来区分“设了什么”和“实际按什么算”的措辞,不等于配置里的字 |
open_files_limit | 文件句柄那一层的额度,连接数生效值要拿它的有效值减 810 去比 |
| 810 | 手册原句里出现的扣减常数,来源手册未解释 |
CONNECTION_ADMIN | 拥有它才能用上max_connections + 1里多出来的那一个名额 |
SUPER | 旧权限名,手册标为已弃用,但仍被认作那个名额的凭据 |
| 诊断通道 | 那个 +1 名额的实际用途:满的时候让管理员还能进去看 |
SHOW PROCESSLIST | 手册在“名额满怎么办”里点名的诊断入口,行数即当前连接数 |
| 连接池 | 应用侧预先建好一批连接、用时借、用完还,使连接数跟着配置而不是请求数 |
| 长连接 | 建一次一直留着,把名额占用从瞬时变成常驻 |
| 空闲连接 | 不执行语句但上下文仍在,占用与刚建立时相同 |
| 排除法第一步 | 同一时刻换个账号能不能连——用来分开“路不通”与“名额满” |
写在最后:这篇用到的资料
写这篇文章时,把 MySQL 8.0 手册的
max_connections条目和故障排查附录 B.3.2.5 对着读,才发现“默认 151”这句话后面还跟着半句“取较小的那个”——多数人只记住了前半句,于是改完发现没变化。顺手也整理了几份配套的东西:
- 数据库连接上限排查卡:两个数取较小者的算法、+1 名额的判据、五步自查顺序
- Web 安全学习路线图:从基础打牢到安全管理,四个阶段各学什么
- 常用靶场清单:每个靶场练什么、适合哪个阶段
资料是我自己整理的,放在下面这个码上,扫码即可获取:
添加时备注「靶场」,优先通过。
拿到之后建议先看数据库连接上限排查卡那一份,下次再碰上“连不上”,先花十秒分清是路不通还是名额满,比反复重启服务有用得多。