最近在排查一个线上服务的时候发现了一个很有意思的现象明明业务量不大数据库却经常报Too many connections重启服务能管一会儿过几个小时又复发了。当时第一反应是是不是连接池配太大了结果一查根本不是。这个事让我重新把 MySQL 连接数这块捋了一遍从查询方法到配置细节到问题排查踩了不少坑也积累了一些实打实的经验。这篇就把 MySQL 连接数的常见操作和底层逻辑完整梳理一遍希望能帮到正在被同样问题折磨的朋友。1. 为什么连接数会成为问题——先从一个真实场景说起MySQL 的并发能力很大程度上取决于连接数的配置和实际使用情况。在大多数默认安装中max_connections这个参数是 151MySQL 5.7 及之前是 1008.0 开始是 151听起来不少对吧但对于一个稍微有点规模的系统这个数字真的很紧张。假设你有 3 个应用节点每个节点连接池的最大连接数是 50理论上峰值就需要 150 个连接数据库这边 151 的上限马上就到顶了。如果再有个后台任务或者 BI 系统来凑热闹连接数瞬间爆掉所有新请求直接排队报错整个服务跟着雪崩。当时我遇到的情况是某公司的一套内部管理系统日活不算高大概就一两百人在用但页面打开慢、时不时报连不上数据库。登录 MySQL 一看Threads_connected稳定在 300 以上而max_connections只有 151。这意味着有一半以上的连接是无效的——它们不是真的在执行查询而是因为程序里某个环节没有正确释放连接或者连接池的空闲连接没有回收机制一直占着坑。那次的排查结论很典型不是连接数不够而是连接被“吃”掉了。所以连接数问题本质上不是单一的技术问题。你首先得能准确地把当前的状态查清楚理解它为什么会变成这样然后才知道下一步该调什么、怎么调。这也是我把查询、配置、排查三件事放到一起讲的原因它们是一套完整的操作闭环。2. 查询当前连接数——几个命令和表的实际用法定优劣2.1 最基础的三条命令Threads_connected、Threads_running、Max_used_connections连上 MySQL 之后最直接的查询是SHOW STATUS LIKE Threads_connected;也可以多条一起看SHOW STATUS WHERE Variable_name IN (Threads_connected, Threads_running, Max_used_connections);这三个状态值的含义要搞清楚Threads_connected当前有多少个连接处于“已建立”状态不管是正在执行 SQL 还是空闲挂起都算在内。这是最常用的一个指标。Threads_running当前真正在“干活”的线程数也就是说正在执行查询的线程。如果这个值长时间大于 CPU 核数说明有 SQL 在疯狂消耗资源或者锁等待严重。Max_used_connectionsMySQL 启动以来出现过的最大连接数峰值。这个值非常有用它告诉你系统在历史上最紧张的时候用了多少连接可以作为调整max_connections的参考依据。我的习惯是每隔几秒执行一次这三条命令连续观察一段时间基本就能判断出连接曲线是平稳的还是锯齿状的。如果在低峰期Threads_connected也一样高居不下那基本可以认定存在连接泄漏如果只是在高峰期瞬间上来那就要从连接池配置和 QPS 的关系去考虑。2.2 通过 information_schema.processlist 看连接的“成分”只看到一个数字解决不了问题你必须知道这一堆连接是从哪来的、在干什么、已经持续多久了。information_schema.processlist这张表就是干这个的SELECT id, user, host, db, command, time, state, info FROM information_schema.processlist ORDER BY time DESC;这里有几个字段值得留意command连接当前的状态。最常见的是Sleep空闲等待还有Query正在执行、Connect正在建立连接等。如果大量连接都是Sleep而且time很长那很可能就是连接没释放的典型特征。time当前状态持续的时间单位是秒。一个空闲连接 sleep 了三五分钟本身就是可疑信号。state如果正处于某个内部处理阶段这里会显示详细信息如Waiting for table metadata lock、Sending data等。如果卡在某些状态上时间很长就要结合具体 SQL 和表去分析。在复杂场景下我通常还会按照不同的维度去聚合比如按用户分组看谁占的连接最多SELECT user, COUNT(*) AS cnt FROM information_schema.processlist GROUP BY user ORDER BY cnt DESC;按来源 IP 分组也同样常见特别是排查某个应用节点是不是异常时SELECT SUBSTRING_INDEX(host, :, 1) AS host_ip, COUNT(*) AS cnt FROM information_schema.processlist GROUP BY host_ip ORDER BY cnt DESC;这个过程就像盘点一个仓库你不仅要看总库存连接数还要看哪些货物积压了Sleep 连接、哪些大单子占用了大量通道长时间 Query、是不是某个供应商应用节点或账号送来的货特别多。没有这些明细数据后面所有的优化和配置都只是拍脑袋。2.3 细看当前会话状态SHOW PROCESSLIST 和 performance_schemaSHOW PROCESSLIST是information_schema.processlist最直接的命令行入口它通常只显示你自己有权限看到的线程如果要看全部需要 PROCESS 权限。一般排查问题直接执行这一条就够SHOW FULL PROCESSLIST;加上FULL可以显示完整的 SQL 语句不会截断。这是日常诊断中最常用的命令没有之一。另一个更轻量的替代是通过performance_schema.threads表来看线程级数据比如每个线程对应的连接、内部线程类型前台连接还是后台线程SELECT THREAD_ID, PROCESSLIST_ID, NAME, TYPE, PROCESSLIST_USER, PROCESSLIST_HOST, PROCESSLIST_COMMAND, PROCESSLIST_TIME FROM performance_schema.threads WHERE PROCESSLIST_ID IS NOT NULL ORDER BY PROCESSLIST_TIME DESC;这个表的优势在于能区分内部线程如thread/sql/one_connection之外的各种后台线程和真正的用户连接在某些版本和场景下比 processlist 更全面。不过对绝大多数日常运维来说SHOW FULL PROCESSLIST已经足够好用了不必每天盯着 performance_schema 看。2.4 连接数相关的基础参数查询查询完连接数现状后紧接着需要确认配置的边界。最核心的几条配置直接用SHOW VARIABLES看SHOW VARIABLES LIKE max_connections; SHOW VARIABLES LIKE max_user_connections; SHOW VARIABLES LIKE wait_timeout; SHOW VARIABLES LIKE interactive_timeout;这几个参数的含义max_connections实例最大连接数全局限制。max_user_connections单个 MySQL 账号的最大连接数限制默认 0 表示不限制所有用户共享全局连接数。如果某个账号明显地连接数异常高可以通过它单独限制。wait_timeout非交互连接的超时时间默认 28800 秒8 小时。理论上一个睡眠连接最长可以占 8 小时不动这其实是隐患。interactive_timeout交互式连接比如命令行客户端直接连的超时时间也是默认 28800 秒。查询只是第一步看到这些参数后你心里要有一条清晰的判断链业务正常需要多少并发连接 → 当前连接池配置是否合理 → MySQL 侧的上限是否足够 → 空闲连接能不能及时回收。这些参数和状态值配合起来才能真正回答“连接数为什么不对劲”这个问题。3. 连接数的配置策略——从参数含义到典型配置方案3.1 max_connections 怎么调临时修改与永久修改如果排查后发现当前环境确实需要更大的连接数修改max_connections有两种方式。临时生效的方式是SET GLOBAL max_connections 1000;这种方式不要立刻重启适合在对系统稳定性没有把握的情况下先试探性调整。但要注意SET GLOBAL只对后续新建立的连接生效已经存在的连接不受影响而且MySQL 重启后配置会丢失。永久生效需要修改配置文件。Linux 环境一般是/etc/my.cnf或/etc/mysql/mysql.conf.d/mysqld.cnfWindows 环境是my.ini。在[mysqld]段下添加或修改[mysqld] max_connections 1000改完需要重启 MySQL 服务才能正式加载新的参数。判断配置是否生效的查询方式之一是SHOW VARIABLES LIKE max_connections;当然修改配置前后建议用SELECT max_connections;和SELECT global.max_connections;一起验证避免因为当前会话和全局变量不一致产生误判。我自己的经验是不主张直接无脑把max_connections设成一个很大的值。连接数并不是越多越好。每个 MySQL 连接在服务端都会占用一定内存连接越多意味着可能的内存占用越大MySQL 的调度压力也越大。一个连接大概占 1~2 MB 还是至少 256 KB 这事其实取决于你的参数配置和堆积任务情况但有一点是确定的过多的连接只会加剧资源竞争不会提升查询速度。比较合理的做法是先定位连接的构成查清是业务高峰确实需要这么多还是程序没有正确释放再做调整。3.2 和连接数强相关的隐藏参数back_log、thread_cache_size、max_user_connections除了max_connections本身还有几个参数在实际配置中值得一起考虑。back_log在 MySQL 停止接受新连接之前可以堆积在协议栈中的连接请求数。它间接决定了高峰瞬间连接队列能容纳多少等待者。默认 50如果有突发性连接高峰适当调高到 100 或 200 可以避免丢连接。但是有个小限制该参数并非越大越好过大反而可能掩盖真实的连接尖峰问题。thread_cache_size连接销毁后线程不会立刻彻底销毁会缓存一部分供新建连接复用。这个值代表可缓存的线程数量。默认值往往很小8 或 16而如果应用频繁创建和断开数据库连接比如没有使用长连接池的程序或者短连接型脚本线程缓存就显得非常重要。调大可以降低频繁创建线程的开销。max_user_connections限制同一个账号最多能建立多少连接。这在多人共用同一数据库实例、统一使用一个业务账号的场景下非常有用。比如设置SET GLOBAL max_user_connections 200;或者是写进配置文件。这个参数不能超过全局的max_connections本身是对账号维度的资源隔离手段防止某个应用账号占用了全部连接额度。3.3 配置方案要结合业务形态一个可参考的计算思路前面说过不要盲目把数值调大那么到底怎么确定一个比较合适的值我提供一个实际用过的估算思路。先说内存维度。每个连接占用的内存有一部分是固定分配有一部分是按需分配。按需分配的主要来自各种 buffer例如sort_buffer_size默认 256 KB不同版本有差异join_buffer_size默认 256 KBread_buffer_size默认 128 KBread_rnd_buffer_size默认 256 KB如果一个连接同时开启了很多排序、关联等操作一个连接额外占用可能达几 MB。不过正常业务不会每个连接都把这些 buffer 拉满所以估算时可以按每个连接平均占用 1~3 MB 来粗算或者保守一点按每个连接占用 2~3 MB 计算。假设你的数据库可用内存是 8 GB操作系统保留 20%MySQL 自身缓冲innodb_buffer_pool_size设置 4 GB那么留给连接的内存大约还有 8 × 0.8 - 4 2.4 GB。如果每个连接按 3 MB 算理论可支撑约 800 个连接。但实际操作上不能拿这个数值直接作为max_connections。你需要参考Max_used_connections的峰值再留出 30%~50% 的余量。比如历史上看到最高用到过 300 个那配置在 400~500 左右是比较合理的。再说业务形态维度。如果应用侧使用的是连接池那么数据库的max_connections一定要大于所有应用节点连接池最大连接数的总和否则就是自己给自己的连接池设了个天花板。比如 5 个应用节点每个节点连接池最大 100那么数据库的max_connections至少要有 500 以上才行再加上后台任务和其他临时连接700 算是一个比较稳的值。3.4 修改配置后的生效验证调整完配置不能拍拍手就结束了验证环节很重要。我通常会这样做执行SHOW VARIABLES LIKE max_connections;确认新值已生效。找一个客户端连续建立比原来上限更多的连接用一条简单的SELECT 1验证新连接能正常建立。观察Threads_connected是否按预期增长同时观察Threads_running是否正常如果连接数高了但 running 不高说明连接虽多但没有真正在执行任务存在空闲浪费。把监控曲线记录下来和调整前的峰值对比确认新的上限能覆盖高峰需求。如果是在晚上低峰期调整最好在第二天的高峰时段再检查一次因为实际的连接压力往往都在白天业务高峰才能体现出来。很多时候白天观察到的新峰值会挑战当时的估算。4. 常见连接数问题排查——从现象到根因的完整路径4.1 连接数高但不跑任务八成是连接泄漏回到开头那个线上案例。当时Threads_connected长期在 300 以上但是Threads_running常年不到 5。这中间的差值不是“高并发”而是“占着茅坑不拉屎”。我当时的排查步骤是先执行SHOW FULL PROCESSLIST;看每个连接的Command是什么。结果 90% 都是Sleep也就是空闲连接。按time排序发现有很多Sleep连接的时间超过了 30 分钟。检查应用的数据库连接配置发现连接池的空闲回收时间没有配置导致连接用完不归还或者在归还之前就阻塞住了。进一步查代码发现某个定时任务里创建了新的数据库连接但在任务结束时没有显式关闭导致连接越积越多。这也是我想强调的一点绝大多数连接数问题真正的原因都在应用侧而不是数据库配置本身。数据库只是被动接受连接真正决定连接数量和生命周期的是应用代码和连接池策略。所以排查连接数问题时不能只顾着调 MySQL 参数一定要往应用代码、连接池配置方向深挖。4.2 大量 Sleep 连接是“假空闲”等待超时和不释放需要分清Sleep连接增多一般有几种不同成因连接池的空闲连接数量池得太大而且空闲超时时间没有配置或者配置过长导致连接一直挂在数据库上。应用代码中创建了连接但忘记关闭最常见的是在异常分支中忘记 close。某些 ORM 框架或中间件的连接池实现存在 bug或者配置不生效。长连接型业务场景下一个连接确实会长期挂着但数据库的wait_timeout太长导致连接“该释放时没释放”。针对Sleep连接过多我常用的处理手段是先看应用连接池配置有很多框架支持“空闲连接回收”和“连接最大存活时间”设置把这些时间调小可以自动清理空闲连接。数据库侧也可以做兜底比如把wait_timeout从默认的 28800 调成 300也就是 5 分钟。这样超过 5 分钟完全没活动的连接会被服务端主动断开。延长了应用侧的感知就是偶尔会有“连接断开”的报错所以这个值要结合应用的重连机制一起设置不能拍脑袋调得太激进。在监控上分开跟踪Sleep和Query的数量一旦 Sleep 数量持续增长就触发告警。这里要特别提醒改wait_timeout是一个明显带副作用的操作。如果应用连接池很活跃5 分钟空闲断开可能还好但一些老的应用没有自动重连机制连接被 MySQL 断开后应用并不知道下次请求才会发现异常。所以空闲超时参数要调但一定要先确认应用连接池有重连能力。4.3 连接数突然飙升的排查思路有些故障是“平时好好的突然某一天爆了”。对于这种突发情况我的排查顺序一般是先查询SHOW PROCESSLIST看当前是什么 SQL 在跑、是谁发起的连接。如果很多连接来自同一个应用节点 IP那大概率是那个节点上的代码逻辑出问题了比如死循环里建连接、大批量任务同时启动。用SHOW STATUS LIKE Max_used_connections;看峰值有没有突破预先设置如果突破了说明场景需求超出了预期需要调max_connections和业务侧限流。检查是否有大量慢查询。慢查询会把连接的占用时间拉长导致单位时间内需要的连接数变多。同一时间只能执行这么多 SQLSQL 慢连接就得一直挂着后面的请求只能排队等连接。看锁竞争。如果某个表被锁了后来的查询全部等待等待中的查询也占着连接不释放会导致连接数快速触顶。这种场景下调高max_connections只是治标不治本。找到是 SQL 慢、锁竞争还是连接池配置导致的才是治本。4.4 连接数问题的排查工具和技巧排查连接数问题除了 MySQL 自身的命令我还会配合操作系统和监控工具一起看用ss -s或者netstat -ant | grep 3306 | wc -l从网络层角度看 TCP 连接数。这个观察角度能帮你确认MySQL 内部看到的连接数是否和系统层面的连接数一致排查是否存在连接“卡在 TCP 状态”等情况。用SHOW ENGINE INNODB STATUS\G看事务和锁等待的细节特别是连接数上升和锁等待同时发生的时候这里面能有很详细的信息。慢查询日志必须开着。很多连接数问题的深层原因是慢 SQL连接只是受害者。没有慢查询日志排查效率会低很多。有条件的团队建议把Threads_connected、Threads_running、Max_used_connections、Aborted_connects都接入监控系统至少保留近 90 天的趋势数据。排查告警的时候回看历史趋势非常有价值。比如你能通过监控发现每周三下午 3 点连接数会周期性上涨再结合业务日历就能找到原因。4.5 连接数相关常见问题速查表现象常见原因排查入口推荐处理方向大量 Sleep 连接连接池空闲连接未回收 / 代码未释放连接SHOW FULL PROCESSLIST按 time 排序调连接池空闲超时代码补 close关注 wait_timeout连接数随流量瞬间暴增短连接频繁创建销毁 / 突发批量任务看 Threads_running 是否同步上升应用侧用连接池任务做分片和限流连接数爬满 max_connections峰值需求超过预期 / 锁等待堆积观察 Max_used_connections 与慢查询先排查锁和慢 SQL再评估是否调顶配单账号占用过多连接某应用节点连接泄漏按 user 聚合 processlist设置 max_user_connections 做兜底连接数不高但报错 Too many connections有连接被占用但不可见全局和账号维度分别看检查是否有跳过验证的内部连接或算错连接池总大小5. 连接数规划与监控告警的实操建议5.1 给连接池和 max_connections 分别定标数据库的max_connections和应用连接池是两个层面两者需要配合起来定标。应用连接池有几个关键参数以常见的连接池配置为例一般需要关注初始连接数连接池启动时创建的连接数不用太多够用就好。最小空闲连接数连接池会尽量维持至少这个数量的空闲连接避免每次请求都要现建连接。最大连接数这是核心。它决定了单个应用节点的最大数据库并发能力。这个值和数据库全局的连接数上限必须匹配。连接最大存活时间防止连接无限期存活建议配上。空闲连接检测时间定期检测空闲连接配合最小空闲连接数回收多余连接。我曾经维护的系统数据库max_connections配的是 600应用有 6 个节点每个节点连接池最大是 80理论最大是 480余量 120。这个余量看起来挺合理但后来发现数据库实例上还有一套定时报表系统它也会建连接有时一次建 200 个以上结果直接在高峰期把连接数顶爆了。后来把报表系统的连接限制单独做了隔离才彻底解决问题。所以在定标时不要只算应用主链路的连接池还要把其他团队的连接需求、后台脚本、BI 工具、手动运维连接全部算进去。最好在连接建立层面统一约束所有连接都必须走同一个账号或同一组账号并设置max_user_connections或者通过中间层统一管理数据库连接。5.2 建立连接数监控和告警的合理阈值监控告警不能只设置“连接数接近 max_connections 才告警”到那时候往往已经晚了。我的经验是设置多层阈值第一层Threads_connected达到max_connections的 70% 时告警提示“资源水位偏高”提前排查原因。第二层Threads_connected达到max_connections的 90% 时高优先级告警需要马上处理。第三层Threads_running长时间超过 CPU 核心数时告警提示可能存在 SQL 性能问题或锁竞争。第四层Max_used_connections每 24 小时自动统计如果持续逼近max_connections说明当前配置不太够用需要提前扩容或优化应用。另外Aborted_connects这个状态值也很重要它统计了多少次连接建立失败。如果这个值快速增长说明连接数达到上限后不断有客户端尝试连接被拒绝是“连接爆了”的一个前兆信号。5.3 从“被动救火”到“主动管理”的几个习惯说到底连接数管理不是一次性操作它是数据库日常运维的一部分。我建议养成这么几个习惯每周至少看一次Max_used_connections的变化趋势看看有没有在不知不觉中突破之前的峰值。每次调整完连接数相关参数都记录下来包括调整前现象、调整后的监控截图、生效时间点。这样以后再出问题能快速回忆起当时的上下文。要对应用节点数量、连接池配置、数据库max_connections三者之间的“账”了然于胸。有一个地方改了另外两个地方要同步评估。定期检查连接数相关的工作流如果有人申请了数据库账号是否有设置max_user_connections如果新上线了应用模块是否做过连接数测算这些都是隐性风险点。我曾经踩过最深的坑就是只依赖数据库侧参数忽略了应用侧连接池配置结果max_connections调了三次问题依旧。后来干脆建立了一个“连接数变更登记表”每次调整都写清楚原因配合监控图归档。过了一段时间回头看发现绝大多数连接数波动根因都在应用代码和连接池策略上数据库参数本身只是被波及的那个“顶”。6. 一个有点特殊但很实用的角度从 TCP 层和进程层反推连接很多人排查连接数问题习惯只盯 MySQL 内部的命令和状态值但有些疑难点必须跳出 MySQL去操作系统层面验证。比如有一次应用报告连接失败但 MySQL 内部Threads_connected离max_connections还有不少空间。后来用netstat查 3306 端口的连接发现在建立状态的 TCP 连接数远大于 MySQL 的Threads_connected。这说明有些连接卡在了网络层或者握手阶段没有真正成为 MySQL 的业务连接。这种情况要看 TCP 的连接队列是否满了涉及操作系统的somaxconn和 MySQL 的back_log参数有时候调优的重点已经不在 MySQL 本身了。再比如当一个高负载实例上出现连接数分布不均匀时用ss -tan | grep 3306 | awk {print $5} | cut -d: -f1 | sort | uniq -c | sort -rn可以快速看到是哪个客户端 IP 占用了最多的连接。这个方法比在 MySQL 里查host更快而且能看到一些未被识别的网络连接。从 TCP 层和进程层看连接说白了是多了一个视角。MySQL 内部的连接是逻辑层概念TCP 连接是物理层概念两者不一致的时候问题往往出在传输层、操作系统参数或者客户端连接框架上。对于长期在用 MySQL 的人这个视角值得保留。7. 最后两个实际心得第一个心得是关于“连接数到底是不是问题”。我在实际运维中见过很多次这种情况凌晨 2 点数据库在线人数很低但Max_used_connections依然达到上限触发了告警。查了半天发现是一个定时备份任务在跑它用了一个没做连接复用的脚本一次性拉起了几百个连接备份完就全部释放。把那个脚本改成串行或者限制并发后问题立即消失。这提醒我连接数调优的目标不是“把数值调大”而是“让连接数贴合真实需求”。在做任何调整之前先问自己一句——当前这些连接都是必须的吗第二个心得是关于“配置参数从来不是孤立的”。修改max_connections时如果你不同时关注max_user_connections、back_log、thread_cache_size、wait_timeout这些相关参数往往会出现按下葫芦浮起瓢的情况。我自己习惯把这些参数列成一组修改时统一审视而不是单点调整。比如调大了max_connections但back_log还很小高峰期还是会有连接丢失调大了thread_cache_size对短连接多的场景收益非常明显。这一组参数协同配置才是一套完整的连接数管理方案。最后给一条最实在的建议在别人让你调max_connections之前先把SHOW FULL PROCESSLIST的结果多抓几遍。几乎所有连接数问题都能从进程列表里找到蛛丝马迹。数据永远比猜测可靠。希望这篇经验对你排查 MySQL 连接数问题有帮助少踩几个我已经踩过的坑。