ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MySQL连接数上限深度解析:从默认151到系统资源极限

MySQL连接数上限深度解析:从默认151到系统资源极限 1. 一个被问烂了的问题为什么还是值得认真聊MySQL 最多能有多少连接这大概是 DBA 面试里出现频率最高的问题之一。我刚带团队的时候也经常拿这道题去问新人十个人里有八个能背出 151 这个默认值但再往下问一句这个上限是谁定的、连接数满了会发生什么、怎么判断该调多大就立刻卡壳。说实话这题看着简单背后牵出来的是一整套连接管理机制、线程模型、系统资源核算和运维排查思路远不是一个show variables like max_connections就能说清的。和不少同行聊过之后我发现真正有价值的不是记住几个数字而是理解这张连接配额的血缘关系谁在限制它、谁在消耗它、谁在排队等它以及当配额见底时为什么你的应用会突然报Too many connections。这篇文章我打算把这块彻底讲透从参数本身一路拆到 Linux 文件句柄、线程栈、内存预算、连接风暴、连接池调优最后给出一套可以直接照着做的排查和调优清单。不管你是开发、DBA 还是刚入门的运维按这条线走一遍以后再碰连接数问题心态会完全不一样。我自己这些年排过不少连接相关的故障从几十个连接的内部系统到上万连接的游戏后端都折腾过。有一个感触特别深连接数问题从来不是单纯的数据库问题它是应用、网络、操作系统和数据库四方博弈的结果。今天这篇就是把这些博弈点一个个摊开来讲。2. max_connections 的默认值和它到底限定了什么2.1 先摆事实151 和 100000 是怎么回事MySQL 的max_connections参数控制的是 MySQL 服务器允许同时保持的客户端连接总数上限。默认值在 5.7 和 8.0 里都是 151这个数字主要考虑的是历史上单机 MySQL 的并发能力同时给操作系统和其他服务留出余量。很多人在网上能看到有人把值调到 100000 甚至更高那通常是压测环境或者特定的大内存高并发业务场景下的激进配置不代表生产环境也应该这么干。这里要特别区分两个概念总连接数和活跃连接数。max_connections限制的是所有处于 Sleep 或者 Query 状态的连接总和。一个连接建立后如果暂时没有请求它会处于休眠状态同样占用一个连接名额和一部分线程资源。所以你在show processlist里看到的绝大多数连接可能都是 Sleep这些休眠连接不干活但依然占着茅坑。很多连接数爆掉的场景并不是查询真的很多而是应用侧连接池配得太大一堆连接建立之后又不释放把连接配额慢慢吃光了。下面这张表的数值是 8.0 版本下的默认情况供你心里先有个谱项目默认值说明max_connections151最大并发连接总数max_user_connections0不限制单个用户最大连接数0 表示不受限max_connect_errors100单主机允许的连续失败连接次数超过后会被临时封禁thread_cache_size取决于版本和系统线程缓存数量影响新建连接的速度back_log取决于版本和系统连接请求在握手完成前的排队长度注意max_user_connections这个参数生产环境里有时候一台应用服务器的 IP 会集中建立大量连接如果业务账号是共用的很容易导致某一个应用把连接全占完。通过给关键账号单独设置这个值可以起到隔离作用。我遇到过一个典型案例两个团队共用一个数据库账号其中一个团队发版时连接池配置失误瞬间建了三千个连接直接把另一个团队的查询全部阻塞。后来给每个应用单独建账号并按业务峰值分配max_user_connections类似的事故再没发生过。2.2 连接数的计算逻辑和启动期配额搞清楚连接数有多少不能只看max_connections还要看max_connections 1这个隐含规则。MySQL 在设计上给自己保留了一个超级权限连接的余地即便连接数已经达到上限依然允许具有SUPER权限8.0 里是SYSTEM_USER相关权限的账号再建立一个连接。这样做的目的是让 DBA 在连接满的时候还能登录进去排查问题。实际验证也简单。你可以先故意把max_connections调成一个很小的值比如 3然后用普通账号把连接打满再用 root 账号去连接你会发现 root 还是能连上。但这里有一个很坑的细节如果连接数已经爆满你尝试用 root 连接时MySQL 依然会先报一次Too many connections然后内部才会尝试释放预留连接给你。很多新手看到这个报错就以为 root 也进不去了直接重启数据库其实多试一两次就能进去。这个坑我排障时见得太多了值得单拎出来说一下。另外要留意 8.0 之后权限体系的变化。MySQL 8.0 把一部分超级权限拆成了更细粒度的权限比如CONNECTION_ADMIN。如果你的账号在 5.7 里靠SUPER权限能突破连接限制升级到 8.0 后可能需要额外授予CONNECTION_ADMIN才能达到同样效果。数据库升级之后有些脚本会莫名连不上很多时候就是因为这个权限拆分而不是连接数本身出了问题。3. 隐藏的上限来源操作系统文件句柄才是真正的铁门3.1 从一条报错说起有一类连接问题特别让人迷惑MySQL 配置文件的max_connections明明已经调到 5000 了show variables里也显示 5000但连接数涨到 2000 出头就再也上不去错误日志里还会出现类似Cant create a new thread或者Too many open files的提示。如果你也遇到过这种情况大概率是撞上了操作系统层的限制。每个客户端连接在 MySQL 内部对应一个 socket 连接这个 socket 就是一个文件描述符File Descriptor简称 FD。与此同时MySQL 还需要打开表文件、日志文件、临时文件等这些都要占用 FD。所以连接数上限真正能到多少不仅取决于max_connections还取决于 MySQL 进程能打开的 FD 上限。具体的换算关系可以简化成这样一个式子所需 FD 数量 ≈ 连接数 × 单连接 FD 消耗 基础文件占用其中单连接 FD 消耗通常是 1 到 3 个因为除了 socket 本身还可能有临时分配的文件或管道。基础文件占用则包括系统表空间、redo log、binlog、每张表的 .ibd 文件等。表数量越多基础占用越高。如果拿一个人口来打比方max_connections相当于酒店系统允许登记的最大房客数量而文件句柄限制相当于酒店大楼实际的房间和床位总量。系统参数说能住 5000 人但楼只有 2500 个床位那系统参数就成了空头支票。3.2 实际操作里怎么检查和调整检查当前 MySQL 进程的 FD 上限可以直接看进程的 limits# 找到 mysqld 的进程号 pidof mysqld # 查看该进程的资源限制 cat /proc/$(pidof mysqld)/limits | grep open files如果输出里的 soft limit 和 hard limit 都是 1024 或者 4096那基本可以断定连接上不去是因为这里太小了。常见发行版里默认的ulimit -n都不够大需要调。调整方式分两层。第一层是系统全局和用户级配置编辑/etc/security/limits.confmysql soft nofile 65535 mysql hard nofile 65535第二层是 systemd 托管下的配置。现在主流发行版都用 systemd 跑 MySQL单纯改 limits.conf 可能不生效还需要在 service 文件里追加[Service] LimitNOFILE65535改完别忘记执行systemctl daemon-reload再重启 MySQL。很多人改了 limits.conf 发现没效果就是因为忘了 systemd 这一层会覆盖它。这里有一个我亲身踩过的坑把LimitNOFILE65535和max_connections同时调大之后连接数确实上去了但系统负载也跟着飙高最后发现是 MySQL 打开的线程数量过多导致上下文切换开销暴涨。文件句柄是打开了新的瓶颈又跑到了线程调度上。所以我现在的习惯是改完 FD 之后盯着vmstat的cs列和top里的 CPU 使用率再观察一周确认没有新的瓶颈出现再继续压测。3.3 连接建立的完整链路从 TCP 到 MySQL 线程为了让为什么一个连接会消耗系统这么多资源这件事更直观我把一个连接从建立到执行的完整链路过一遍。它大致要经过这几个环节TCP 三次握手完成socket 建立落入内核的 accept 队列。MySQL 的监听线程从队列里取出连接请求分配一个连接对象。连接对象进行握手验证包括协议版本协商、用户名密码校验、权限加载等。验证通过后MySQL 从线程池或线程缓存中取出一个工作线程来处理这个连接上的查询。连接进入空闲状态时线程可以归还到线程缓存连接本身仍然保持。在这条链路上至少有四个资源点和连接数直接相关socket 文件描述符、内存中的连接对象、线程栈空间、认证过程需要的 CPU 和内存。任何一个环节被顶满都会表现为连接失败或者请求变慢。这也是为什么我不太认同只要把 max_connections 调大就能解决一切并发问题这种说法。调大参数只是扩大了入口但入口之后的每条路都被系统资源约束着。4. 线程模型和连接风暴为什么连接数变多后数据库反而更慢4.1 线程池和 thread_cache_size 的作用MySQL 经典的连接处理方式是每连接一线程也就是一个客户端连接独占一个工作线程这个线程负责读取该连接上的命令并执行。连接多了线程自然就多。频繁地创建和销毁线程需要做系统调用、分配内核栈开销不低尤其在高并发短连接场景下这部分成本非常可观。为了缓解这个问题MySQL 提供了thread_cache_size参数用于缓存空闲线程。当连接关闭时工作线程不立即销毁而是放回缓存等待下一个连接复用。默认值在不同版本和系统上有差异通常不大。我之前在压测时试过把它从默认值调到 64、128、256 几个档位发现对短连接场景的提升很明显尤其是连接建立速率但调到 256 之后收益开始递减因为缓存线程本身也要占内存。再来就是连接池。这里要分清两层连接池应用侧连接池和 MySQL 侧线程池。应用侧连接池比如 HikariCP、Druid解决的是应用与 MySQL 之间的连接复用避免每次请求都新建连接。MySQL 侧线程池则是 MariaDB 和 MySQL 企业版里提供的thread_pool功能它能限制同时执行的线程数让大量连接的场景下只有一部分线程真正在跑从而控制 CPU 争抢。在开源 MySQL 社区版里没有线程池连接多了之后如果大量连接同时活跃线程调度会成为瓶颈。这也就解释了为什么有些系统连接数只有几百时一切都好涨到几千后 CPU 虽然没满但整体吞吐量反而下降。说直白一点MySQL 的每连接一线程模型下线程数和连接数基本呈线性关系而线程调度成本却不是线性的。4.2 连接风暴为什么会瞬间打垮数据库连接风暴是我在实战中见过最致命的一类故障表现形式非常有特点数据库连接数在几秒内冲上几千然后应用开始报Too many connections紧接着是一波超时和重试重试又带来新的连接形成一个恶性循环。风暴的起因通常有两类。第一类是应用重启或发版后应用连接池的初始连接数和最大连接数设置不合理数百个实例同时启动每个实例瞬间建立几百个连接总量轻松突破数据库上限。第二类是数据库短暂抖动后应用侧连接池的空闲连接大量失效触发重连逻辑客户端在重连时往往没有做退避和限制导致连接请求在短时间内的峰值是平时的几倍甚至几十倍。对于这两类问题纯粹调大max_connections是治标不治本。更有效的组合拳是应用连接池的initialSize、minIdle、maxActive都设置成合理的小值不要图省事直接拉满。连接池的connectionTimeout和validationTimeout要短避免应用在数据库故障时无限等待。在应用层做连接获取的退避重试比如失败后按指数退避而不是立即重建。数据库侧开启skip-name-resolve减少连接建立时反向 DNS 解析的超时风险。说到skip-name-resolve这是连接速度优化里一个被低估的参数。MySQL 默认在握手阶段会对客户端 IP 做反向 DNS 解析如果 DNS 服务慢或者不可用连接建立时间可能从毫秒级变成秒级。在连接风暴场景下这会让握手超时集中在同一时间段从而进一步放大问题。我经手的项目里只要确认应用都是通过 IP 连接就会把这个参数打开效果立竿见影。但它有一个副作用processlist里看不到主机名只有 IP。而且如果你在授权表里用的是userlocalhost这种主机名形式开启后可能匹配不上需要提前改成 IP 或通配符。4.3 内存预算连接数上限的另一道账每个连接不只消耗一个线程还消耗内存。连接对象本身、网络缓冲区、线程栈等都会计入 MySQL 进程的 RSS。虽然 MySQL 的线程栈在绝大多数系统上默认是 256KB 到 1MB 不等但连接多起来之后这笔内存相当可观。这里可以做一个简单的估算。假设一个连接平均消耗 2MB 内存包含线程栈和缓冲区max_connections5000时仅连接相关内存就可能达到 10GB。如果服务器的总内存只有 16GB还要分给 innodb_buffer_pool、排序缓冲、join buffer 和操作系统那么这个配置从一开始就是不可行的。很多人压测时把max_connections调得很高然后发现 MySQL 直接被 OOM Killer 杀掉就是没算这笔账。在使用云主机时尤其要注意云服务器的内存往往比同配置物理机小而且有些云厂商默认会限制进程数或线程数。我曾见过一台 4GB 内存的容器里跑 MySQL应用连接池配置了最大 200 个连接结果 MySQL 占满内存被系统自动杀掉日志里却查不到明显的 MySQL 错误最后靠查看内核日志才发现是内存不足。所以调max_connections之前把内存余量算明白比调参本身更重要。5. 从监控到定位连接数异常时的排查链路5.1 一套可以直接复用的排查顺序连接数报警或者出现Too many connections时我建议按下面这条顺序走而不是一上来就改参数或者重启数据库。顺序是有讲究的先看现象再看配置然后看资源最后才动手。先确认连接数的实际状态和构成show processlist;或者查performance_schema里的连接统计。看连接数是否真的打满show status like Threads_connected;和show status like Max_used_connections;对比。看连接的状态分布有多少 Sleep、多少 Query、多少 Connecting。状态分布能快速判断是并发查询真的多还是连接池空闲连接堆积。看 MySQL 错误日志Too many connections、Cant create a new thread、Too many open files是三种完全不同的问题方向。看操作系统资源CPU、内存、文件句柄、线程数、TCP 连接数。这一步能验证是不是 MySQL 之外的资源顶到了上限。定位来源 IP 和账号show processlist里能直接看到 Host 和 User也可以用performance_schema的表做聚合统计找出占用最多的客户端。在 MySQL 8.0 里查询连接状态更推荐用performance_schema的视图比如-- 统计当前连接数 SELECT * FROM performance_schema.threads WHERE PROCESSLIST_ID IS NOT NULL; -- 按用户聚合连接数 SELECT USER, HOST, COUNT(*) FROM performance_schema.threads WHERE PROCESSLIST_ID IS NOT NULL GROUP BY USER, HOST ORDER BY COUNT(*) DESC; -- 按状态聚合 SELECT PROCESSLIST_STATE, COUNT(*) FROM performance_schema.threads WHERE PROCESSLIST_ID IS NOT NULL GROUP BY PROCESSLIST_STATE ORDER BY COUNT(*) DESC;performance_schema的好处是能拿到更细的信息比如线程类型、所属连接 ID、执行状态等而且查询时本身不占用普通连接资源非常适合故障期间使用。相比之下反复执行show processlist在高压力下也可能增加一点点开销可以用但要克制。5.2 一次真实故障的拆解连接数没到上限却全部卡死说一个我记忆很深的案例。有一次某个系统的数据库连接数稳定在 700 左右max_connections配的是 2000看着离上限很远但业务端反馈所有请求都超时数据库 CPU 才用了 30%。我先跑了一遍show processlist发现大量连接的State是Waiting for table metadata lock还有一部分是Sending data。这个状态组合立刻让我意识到问题不在于连接数而在于元数据锁。后续查下去发现是凌晨有人跑了一个长时间未提交的 DDL 或其他事务一直持有表的元数据锁后续所有涉及该表的 DML 全部堵在锁等待上连接越积越多最终把应用侧连接池占满。这个案例给我们的启发是连接数只是表象连接堆积的位置才是核心。同样是连接数上升可能是查询慢、锁等待、网络问题、认证风暴甚至是磁盘 IO 卡顿导致的 hang。如果把连接数当唯一指标很容易走错方向。我后来的习惯是同时盯Threads_running和Threads_connectedThreads_connected高但Threads_running低说明连接在空转方向在应用侧或锁等待两者都高才说明数据库真的在承受并发压力。5.3 临时救急手段和长期方案怎么选连接数爆掉的时候最紧急的一件事是让数据库先喘过气来。除了用预留连接登录之外还有一种方式是动态调大max_connections而不重启数据库SET GLOBAL max_connections 3000;这个操作能立刻缓解一部分压进来的连接请求但要明白它只是给系统争取了排查时间。真正要做的是根据前面的排查链路找到连接暴涨的原因否则临时调大的参数会在半小时后被新的连接风暴再次打满。有些团队喜欢直接重启数据库来清连接这是我的下下策。重启虽然能把所有连接清掉但代价是缓存失效、未提交事务回滚、连接风暴可能马上重演而且重启期间业务是彻底不可用的。除非连 root 都进不去且无法通过任何方式恢复否则不建议走这一步。长期方案我更看重这几件事给应用连接池设置合理的容量和超时给数据库账号按业务设置max_user_connections部署连接数监控并在接近阈值时提前告警对慢查询和锁等待做持续治理减少连接被无效占用的概率。把这四件事做到位比单纯调大一个参数要有效得多。6. 不同负载场景下的连接数规划从几十到上万怎么给6.1 轻量级系统和内部工具150 到 500 足够很多人一上来就想把连接数调到几千但大部分内部系统、管理后台、报表系统根本不需要那么多。这类应用的并发请求量有限连接池也不需要配很大。连接数设成 150 到 500 已经留足了余量而且这个区间的连接数不会对系统资源造成太大压力出问题的概率低。我见过不少内部系统因为连接数配置过大反而引出问题的情况。举个例子一个几十人使用的后台连接池maxActive竟然配了 800数据库max_connections也调到了 2000。平时没事但每次应用发布时连接池预热的几百个连接同时涌进来再加上其他系统立刻就能把数据库的连接负载拉得很高。数据库其实根本不忙纯粹是连接分配不合理。对这类场景我的建议是先把max_connections保持在默认值或稍高一些然后根据监控数据逐步调整。连接池的初始化连接数设置为 5 到 10最大连接数设置为 50 到 100 就已经很充裕。要记住连接数是一种资源不用的连接不应该被建立。6.2 标准 Web 业务如何根据核心指标推算以一个典型的 Web 应用为例假设你有 20 个应用实例每个实例的连接池最大连接数是 50那么理论上最大连接需求是 1000。数据库的max_connections至少要高于这个数字同时还要留出运维查询、备份、监控等额外连接的空间。在这种情况下2000 到 3000 是比较常见的选择。但高于不是唯一原则。你还需要估算数据库单机能承载的并发执行能力。一个粗略的判断方法是看Threads_running。如果数据库的Threads_running长期维持在 CPU 核心数的两三倍以上说明执行层已经过载再多的连接也只会排队。连接数规划不能只看应用侧需求还要看数据库侧的处理能力。连接池参数方面HikariCP 这种轻量级连接池官方文档里的建议很直白最大连接数等于数据库最大可用连接数除以应用实例数再留出适当余量。很多性能专家也提出过一个经验公式连接数等于后端核心数加一到两倍用来把 CPU 喂饱但又不至于让线程上下文切换过多。当然这只是起点最后还是靠压测和监控校准。6.3 高并发或者分库分表场景连接数是全局视角到了高并发场景单库能承载的连接数迟早会成为瓶颈。这个时候的解决方案不是继续调大单实例连接数而是引入分库分表或读写分离把连接压力分散到多个 MySQL 实例上。一个实例撑不住一万个活跃连接但十个实例每个撑住一两千就很容易。分库分表之后要特别注意全局连接数的协调。曾经有个项目在改造前期没有估算总量结果每个分片都按单库承载能力配连接池所有分片的连接数之和远远超过任何一个物理数据库能承受的范围。改造上线后出现一个诡异的现象每个数据库的连接数看着都不到告警线但整个系统大量请求卡顿。后来一查才发现同一个应用连接池的请求被均匀打到了所有分片而部分分片所在的物理机资源已经耗尽。这个问题靠监控单个数据库是发现不了的必须做全局的连接数大盘。读写分离场景下连接数的分配逻辑也不一样。只读实例往往要承担更大的查询并发但查询通常比较短线程占用的时间也短。写实例的连接数不需要特别夸张慢查询和事务锁往往集中在写入端需要额外关注长事务和Sleep连接的比例。把读和写放在同一套连接规划里是常见的失误应该分开设计、分开监控。7. 从一知半解到能自己掌控连接数聊到这里核心的东西基本都过了一遍。最后分享一个我个人的判断标准当你看到一个系统连接数很高不要先急着评价或者调参而是先问自己三个问题连接是活跃的还是空转的瓶颈在 MySQL 还是在操作系统应用侧有没有可能优化掉一部分不必要的连接这三个问题的答案往往比参数本身更能说明问题。随手抛一个很多人忽略的检查技巧MySQL 重启之后Max_used_connections会被清零但performance_schema的events_statements和status_by_thread里仍能查到历史峰值。如果想知道系统曾经扛过多大的连接峰值启动后记录一下这个状态值它会成为你后续规划连接数的重要参考。我自己做容量规划时一定会保留最近三个月内每周的Max_used_connections曲线比拍脑袋给数字靠谱得多。调连接数这件事说到底是让数据库在够用和可控之间取一个平衡。盲目调大往往只是把风险从一个晚上推迟到另一个晚上。真正稳健的做法是搞清楚每个连接背后消耗的资源链路然后用监控数据说话一层层把这个数校准到和业务、机器都匹配的位置。希望这篇梳理能帮你少踩几次坑下次再有人问你 MySQL 最多多少连接时你能从他问的是默认值、配置值还是实际承载值开始反问一句你想问的是哪一个然后真正把这个问题聊透。
返回列表