1. 先搞清楚“连接数”到底有几个数,别把三个指标当同一个
线上告警一响,群里第一句话往往是“连接数是不是满了”。真登录服务器去查,一堆人以Threads_connected为准,另一批人看processlist里的行数,还有人看连接池监控面板上的 active 数——三组数字对不上,讨论直接卡住。这场景太常见了,原因很简单:MySQL 的“连接数”从来不是一个数,而是一组互相有关联但含义完全不同的指标。把它们的定义和关系梳理清楚,后面的排查才有意义,否则你看到的每个数字都像在骗你。
先把结论摆在前面:Threads_connected是此刻已经建立、还没断开的客户端会话总数,包含发呆的 Sleep 连接;Threads_running是此刻真正在执行 SQL、不处于 Sleep 状态的线程数;Max_used_connections是自上次启动以来的历史峰值。这三者的关系类似于“店里有多少客人坐着”“有多少客人正在点单”“今天最挤的时候坐了多少人”——坐着的客人可能只是占着位置喝白开水,不代表后厨忙。
标题里提到的“查询连接数”和“查看连接状态”,其实是两件依次发生的事:先数清楚有多少连接,再逐个看这些连接在干什么。很多人只做了第一步就急着改参数,把max_connections从 500 调到 2000,结果只是把崩溃时间往后推了半小时。真正的价值在第二步——状态字段和 SQL 文本会告诉你,到底是哪些连接在拖后腿。
1.1 当前连接、活跃连接、历史峰值,各自的用处不一样
Threads_connected反映的是资源占用面。每个连接都要占一个线程栈、一份会话缓冲区、一个文件描述符。它高不一定有事,因为大量 Sleep 连接几乎不消耗 CPU,但确实在吃内存和 fd。
Threads_running反映的是当下压力面。它高说明数据库正在被真实请求压着,CPU 占用、锁竞争会跟着上来。业务高峰期这个数字有波动是正常的,只要不长时间贴近max_connections就还行。
Max_used_connections是容量规划的锚点。判断max_connections该设多少,最靠谱的依据就是它——取历史峰值,再留出 20% 到 30% 的余量。凭感觉拍一个 10000 是个坏习惯,因为 MySQL 是每连接一线程模型,参数给得越大,越容易在真正打满时触发内存耗尽,反而让整台实例直接失联,排查窗口都没了。
还有一个容易被忽略的数字:Threads_created。它是累计创建过的线程总数。这个值增长快,说明连接在被反复创建和销毁,连接池没起到复用作用,或者wait_timeout太短导致连接来回重建。一个正常使用连接池的实例,Threads_created一条直线几乎不动;如果它跟心电图一样往上爬,那基本可以锁定应用侧配置有问题。
1.2 为什么上线第一天就该盯连接数,而不是等出事
连接数问题有个特点:它是缓慢劣化的,不会自己恢复。连接泄漏类的故障,从 50 涨到 500 可能花三天,从 500 涨到打满可能只花三小时——因为涨到一定程度后,重试风暴开始叠加,每个失败请求都会再发起新连接,形成正反馈。
我见过一个挺典型的例子:某业务线的批处理任务用了手写 JDBC,异常分支里finally块漏了close()。平时一天跑两次,看不出来;后来任务改成五分钟一次,连接就开始以每次 1 个的速度泄漏。三天后的下午两点,连接池耗尽,接口全线超时,值班的人第一反应是“数据库挂了”,登录上去Threads_connected是 152,max_connections也是 152,Threads_running只有 3。数据库活得好好的,但所有新连接都被拒了。
注意:
Threads_running很低而Threads_connected打满,几乎可以判定是连接泄漏或连接池配置过大,而不是数据库性能问题。这个组合的辨识度很高,先记住它。
1.3 两条命令先跑起来:状态用 SHOW STATUS,参数用 SHOW VARIABLES
不用装任何工具,登录客户端敲两条就够开工:
-- 看当前状态 SHOW STATUS WHERE Variable_name IN ( 'Threads_connected', 'Threads_running', 'Threads_created', 'Max_used_connections', 'Aborted_connects', 'Aborted_clients', 'Connections' ); -- 看关键参数 SHOW VARIABLES WHERE Variable_name IN ( 'max_connections', 'wait_timeout', 'interactive_timeout', 'max_connect_errors', 'back_log', 'thread_cache_size' );这两条的返回值就是排查的起点。我会习惯性地算一个比值:Threads_connected / max_connections。超过 0.7 就该看一眼了,超过 0.85 就别等了,直接排查。这个阈值不是硬标准,但它足够简单,值班的人不需要理解原理也能执行。
2. 核心指标逐条拆解:哪些数字必须盯,哪些看一眼就放
指标多不等于有用。SHOW GLOBAL STATUS输出几百行,全盯着看等于没看。我把和连接相关的分成三类:必须随身携带的、出问题才查的、知道有这么回事就行的。分类依据不是重要性,而是它们各自回答什么问题、在什么场景下才有意义。
2.1 Threads_connected 与 Threads_running 的差值就是“发呆的连接数”
这两个值的差,是判断连接使用效率最直接的口径。差值是 200,说明有 200 个连接此刻什么都不干,占着资源。差值是 0,说明每个连接都在干活,那么即使Threads_connected只有 50,这台库的压力也可能比另一台 500 连接的库大得多。
实际判断时,我会拉一个短周期的采样来看趋势,而不是看单点。比如每 5 秒采一次,采一分钟:
SELECT NOW(), (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Threads_connected') AS connected, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Threads_running') AS running;在 MySQL 5.7 和 8.0 上都可以直接从performance_schema.global_status读,比SHOW STATUS更适合放进脚本里轮询,因为它不会产生额外的结果集开销,也能和别的表做关联。这套采法在写监控采集器时很好用,尤其适合和 Zabbix、Prometheus 这类系统对接时取数。
如果running长期逼近connected,且connected本身也高,那说明这台库是真的在满负荷工作,方向是优化 SQL、加缓存、拆读写;如果connected高但running长期个位数,方向完全相反,是清理闲置连接、修连接池、修泄漏。同一个“连接数高”的告警,背后是两个相反的处理路径,所以这两个指标必须一起看。
2.2 max_connections 不只是一个上限数字,它牵着一串资源
调max_connections之前,先算一笔账。MySQL 每个连接都有独立会话缓冲,虽然是按需分配,但上限是实实在在的。常见的会话级参数包括sort_buffer_size、join_buffer_size、read_buffer_size、read_rnd_buffer_size、binlog_cache_size、thread_stack、net_buffer_length。
按一份常见的默认配置估算单个连接的最大占用:
| 参数 | 常见取值 | 说明 |
|---|---|---|
| sort_buffer_size | 256KB | 排序时分配,按需 |
| join_buffer_size | 256KB | 无索引关联时可放大 |
| read_buffer_size | 128KB | 顺序扫描用 |
| read_rnd_buffer_size | 256KB | 随机读用 |
| binlog_cache_size | 32KB | 写事务时用 |
| thread_stack | 256KB | 线程栈,必占 |
加起来大约 1.2MB 每连接。1000 连接就是约 1.2GB,这还只是会话缓冲,不含全局唯一的 buffer pool。要是有人把sort_buffer_size调到 8MB,1000 连接的理论峰值就是 8GB 以上——这种配置一旦遇到真实并发,机器直接被拖死,而且日志里可能什么都没有,因为 OOM Killer 出手的时候 MySQL 来不及写任何东西。
提示:
max_connections不是越大越好。它的合理值域由物理内存和业务并发共同决定,而不是由“我想让它永远不报 Too many connections”决定。
还有一个隐形的天花板:操作系统的文件描述符限制。max_connections设到 5000,但open_files_limit只有 1024,那实际能开的连接数会被卡住,表现是连接建立失败但状态指标看起来没到上限,非常迷惑。上线前顺手确认一下open_files_limit和服务的LimitNOFILE,能省掉很多莫名其妙的排查时间。
2.3 Aborted_connects、Aborted_clients 与各类 Connection_errors
这组指标专门回答“连接为什么没建成 / 为什么断得不明不白”,在排查间歇性报错时价值极高。
Aborted_connects:尝试连接但失败的累计次数。常见原因有密码错误、账号没有对应 host 的授权、连接超时、max_connections已满、max_connect_errors触发。Aborted_clients:客户端没正常发COM_QUIT就断开。典型场景是应用侧连接池maxLifetime比数据库wait_timeout长,拿到了一条已经被服务端杀掉的连接,第一次使用就报错。Connection_errors_max_connections:因为连接数打满被拒的次数。这个指标只要不是 0,就说明业务已经真实受损了。Connection_errors_accept、Connection_errors_internal:偏系统层,出现往往和 fd 耗尽、内存不足有关。
这几个值怎么用?看增量,不看总量。累计值可能是几个月前的一次事故留下的,看它没意义。取两次采样的差,如果在当前时间窗口内明显增长,才值得追。这个思路和看Slow_queries一样,绝对值没有解读价值。
2.4 wait_timeout 与 interactive_timeout 是连接数的“隐性闸门”
wait_timeout决定一个非交互连接空闲多久后被服务端回收,默认 28800 秒(8 小时)。这个值配上连接池,就容易出问题:连接池认为连接还活着,服务端已经把它踢了;池子把这条“僵尸”借给应用,应用一用就报Communications link failure。
一个比较稳妥的搭配思路是:连接池的maxLifetime明显小于服务端的wait_timeout。比如wait_timeout设 600 秒,池子的maxLifetime设 480 秒,让池子主动先淘汰。反过来配,就是在赌运气。有些中间层(代理、负载均衡)自己还有一层空闲超时,而且往往比数据库更短,这时maxLifetime要按最小那个值来定,否则问题会从“偶发报错”变成“偶发报错且找不到规律”。
interactive_timeout只对交互式客户端(比如命令行mysql)生效,但有个细节:客户端连接时会话级的wait_timeout会被interactive_timeout的值覆盖——这是历史遗留行为,很多人调了参数没生效就是因为这个。改的时候两个一起改,省得来回折腾。
3. 查看连接状态的四种姿势,各有各的适用场景
掌握了指标定义,下一步是看清每个连接在干什么。MySQL 给了四种常用方式,从“随手敲一下”到“写进监控采集脚本”,粒度、性能开销、可编程性都不同。选错工具会让排查效率差好几倍,尤其是连接数已经打满的时候——用开销大的方式去看,很可能连结果都出不来。
3.1 SHOW PROCESSLIST 与 SHOW FULL PROCESSLIST
最直接的一种。SHOW PROCESSLIST只显示Info字段的前 100 个字符,想看完整 SQL 必须用SHOW FULL PROCESSLIST。这个差异踩过坑的人都懂:只看到SELECT * FROM order_detail WHERE user_id = ...开头,剩下的关键条件(比如是不是带了个超大IN列表、有没有走索引)全被截掉了,等于没看到。
输出字段含义值得逐一说清楚:
| 字段 | 含义 | 排查时的用法 |
|---|---|---|
| Id | 连接标识 | KILL时要用 |
| User | 登录账号 | 区分是应用连接还是人工连接 |
| Host | 客户端来源 | 定位到具体机器/实例 |
| db | 当前默认库 | 判断业务归属 |
| Command | 命令类型 | Sleep / Query / Binlog Dump 等 |
| Time | 当前状态持续秒数 | 长事务、长时间 Sleep 一眼看出 |
| State | 内部状态描述 | 判断卡在哪个环节 |
| Info | 正在执行的 SQL | 找慢 SQL 的主力字段 |
Command为Sleep的行,Time就是空闲了多少秒。这个数字和wait_timeout一比,就能知道这些连接还有多久被回收。如果有一批 Sleep 连接的Time都接近wait_timeout,说明有一批应用实例在周期性建连又放着不用,典型的池子配置不当。
3.2 information_schema.PROCESSLIST 的 SQL 化玩法
SHOW PROCESSLIST的局限是没法筛选排序。真正干活时,information_schema.PROCESSLIST更有用,因为它就是一张表,可以随意WHERE、ORDER BY、GROUP BY:
-- 按客户端来源统计连接数,找谁占得多 SELECT SUBSTRING_INDEX(HOST, ':', 1) AS client_ip, COUNT(*) AS conn_cnt, SUM(COMMAND = 'Sleep') AS sleep_cnt FROM information_schema.PROCESSLIST GROUP BY client_ip ORDER BY conn_cnt DESC; -- 找空闲超过 300 秒的连接 SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE FROM information_schema.PROCESSLIST WHERE COMMAND = 'Sleep' AND TIME > 300 ORDER BY TIME DESC; -- 找执行超过 60 秒的非空闲连接 SELECT ID, USER, HOST, DB, TIME, STATE, LEFT(INFO, 200) AS SQL_HEAD FROM information_schema.PROCESSLIST WHERE COMMAND != 'Sleep' AND TIME > 60 ORDER BY TIME DESC;这三条按顺序执行,基本能覆盖八成的现场排查需求。第一条回答“谁占的”,第二条回答“哪些是闲置的”,第三条回答“哪些在拖时间”。第三条尤其重要,因为长时间非空闲的连接往往意味着锁等待或者大查询,是最需要优先处理的。
注意:在连接数已经打满的情况下,查
information_schema.PROCESSLIST本身也需要一个连接。这时候可以先从监控系统看历史趋势,或者用预留的管理账号连进去,再或者通过操作系统层观察。提前准备一个专用的高权限管理账号,专门应对这种情况,是很有必要的习惯。
3.3 performance_schema.threads 与 sys 库视图
MySQL 8.0 上我更推荐走performance_schema。原因有二:一是SHOW PROCESSLIST在 8.0.22 之后本身就是从performance_schema取数了,直接查底层表少一层转换;二是performance_schema.threads能关联更多信息,比如线程类型、所属用户线程还是后台线程。
SELECT THREAD_ID, PROCESSLIST_ID, PROCESSLIST_USER, PROCESSLIST_HOST, PROCESSLIST_DB, PROCESSLIST_COMMAND, PROCESSLIST_TIME, PROCESSLIST_STATE, LEFT(PROCESSLIST_INFO, 200) AS INFO FROM performance_schema.threads WHERE PROCESSLIST_ID IS NOT NULL ORDER BY PROCESSLIST_TIME DESC LIMIT 30;PROCESSLIST_ID IS NOT NULL这个条件很关键,它过滤掉了后台线程,只留客户端连接。不加这个条件,会看到一堆event_scheduler、io_thread之类的内部线程,干扰判断。
如果装了sys库(5.7 之后基本默认有),还有更省事的封装:sys.processlist已经把常用字段和格式化都做好了,sys.session是它的精简版,只保留用户会话。sys.processlist里连“这条 SQL 的执行进度”“是否在等锁”都有现成字段,排查长事务时比手写 SQL 快得多。不过它的底层依赖performance_schema相关 consumer 打开,如果被人关过,视图会返回空,这时回到information_schema兜底。
3.4 四种方式对比,按场景选,不要凭习惯
| 方式 | 优点 | 短板 | 适合场景 |
|---|---|---|---|
| SHOW PROCESSLIST | 零门槛,随手可敲 | 不能筛选,Info 截断 | 快速瞄一眼 |
| SHOW FULL PROCESSLIST | 能看到完整 SQL | 输出大,连接多时刷屏 | 追单条具体 SQL |
| information_schema.PROCESSLIST | 可任意筛选聚合 | 每次查询实时计算,连接多时有开销 | 日常排查主力 |
| performance_schema.threads / sys | 字段全,可关联,开销低 | 需要相应 consumer 开启 | 监控采集、深度分析 |
我自己的习惯是:值班快速确认用SHOW PROCESSLIST,正式排查用information_schema写筛选 SQL,写进监控采集器的用performance_schema。这套分工用了几年,基本没遇到需要换的情况。
4. 连接状态码怎么读:Sleep 到底算不算问题
State字段是这套排查体系里信息密度最高、也最容易被误读的地方。同样一个Sleep,在只读从库上可能是完全正常的,在写主库上可能就是连接池配置烂掉的证据。脱离上下文看状态码,跟看体检报告只看箭头不看数值一样,容易得出错误结论。
4.1 常见 State 的准确含义与判断标准
先把高频的挑出来说清楚:
Sleep—— 连接建立了,当前没有 SQL 在执行。这是状态,不是错误。是否异常取决于数量、持续时间和业务形态。
Query—— 正在执行 SQL,且还没进入具体阶段。通常一闪而过,如果长时间停在Query,说明客户端在慢慢读结果集,或者网络带宽是瓶颈。
Locked—— 正在等表级锁。MyISAM 时代常见,InnoDB 下出现意味着有LOCK TABLES语句或者元数据锁在阻塞。
Waiting for table metadata lock—— 等元数据锁。这个状态杀伤力极大,一个没提交的长事务持有 MDL,后面所有 DDL 和访问该表的语句全部排队。连接数会因为排队而暴涨,但根因只有一个没人管的事务。
Sending data—— 名字容易误导,它不是“发数据”,而是“正在读取和处理数据行”。出现在大结果集、无索引扫描、排序聚合时,是慢查询排查的重点观察点。
Copying to tmp table—— 正在往临时表写数据,通常伴随GROUP BY、DISTINCT、UNION或者大排序。如果Created_tmp_disk_tables也在涨,说明落盘了,性能会差很多。
Waiting for table flush—— 有FLUSH TABLES类操作在等所有正在使用该表的语句结束,又被长查询堵住。这个状态一旦出现,后面会连锁堆积。
判断标准其实很简单:看这个状态上卡了多少连接、卡了多久、有没有继续增长。单个连接卡在某个状态几十秒不一定是问题,几十个连接同时卡同一个状态而且还在增加,就是事故级别的信号。
4.2 Sleep 连接堆积的三种成因,处理方式完全不同
第一种:应用连接池下限配得太高。某个服务minimumIdle设成 50,但它实际并发只有 5,那 45 个连接就长期躺着 Sleep。这种情况看着吓人,其实无害,只是浪费内存。处理方式是调小池子的下限,或者干脆把下限设为较小值、让池子按需增长。
第二种:连接泄漏。借出去的连接没还,池子里可用连接越来越少,应用只能不断新建,最终越过池子上限,或者直接把数据库连接数撑满。特征是Threads_connected单调上升、Threads_running低位徘徊、重启应用后立刻回落。这种必须改代码,光调参数没用。
第三种:wait_timeout太长加上没配池子。每次请求新建连接、用完不关,全靠服务端超时回收。特征是Threads_created持续增长,Aborted_clients时不时涨一点。处理方式是上连接池,并把超时关系理顺。
区分方法:重启应用看数字掉不掉。掉了是泄漏或池子问题,不掉是服务端侧或者别的客户端。这个动作简单粗暴但极其有效,现场排查时优先级很高。
4.3 元数据锁等待:连接数暴涨最常见的“真凶”之一
说它是真凶一点不夸张。一个典型的连锁反应是这样的:某人在业务高峰期执行了ALTER TABLE,或者某个长事务(比如一个跑了几十分钟的报表查询)持有该表的 MDL,ALTER排队;由于ALTER在排队,后面所有访问这张表的语句也跟着排队;排队意味着连接被占用,Threads_connected迅速爬到上限;应用侧发现拿不到连接开始超时重试,重试又发起新连接,恶性循环。
定位方法很直接:
-- 找长时间未提交的事务 SELECT trx_id, trx_state, trx_started, TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS elapsed_sec, trx_mysql_thread_id, trx_query FROM information_schema.INNODB_TRX ORDER BY elapsed_sec DESC; -- 找阻塞链 SELECT * FROM sys.innodb_lock_waits ORDER BY wait_age DESC; -- 5.7 无 sys 库时用 SELECT * FROM information_schema.INNODB_LOCK_WAITS;INNODB_TRX里elapsed_sec很大、trx_mysql_thread_id对应的连接状态是Sleep,基本就是元凶——事务开了没提交,连接闲着但锁没放。把那个线程KILL掉,连带阻塞会瞬间解开,连接数几十秒内回落。
提示:
KILL之前先看清楚是KILL QUERY还是KILL CONNECTION。前者只终止当前语句,事务还在;后者把整个连接断掉,事务回滚。处理 MDL 阻塞时通常需要后者,但代价是可能触发大量回滚,大事务回滚可能比阻塞本身更耗资源。大事务场景下贸然KILL CONNECTION是有风险的,这个决定要慎重。
5. 一次完整的连接数排查实录:从告警到恢复
光讲指标容易空。下面这套流程是我实际处理连接数告警时用的顺序,从收到告警到确认恢复,大致十几分钟,记录下来供参考。场景设定为某业务系统的主库连接数告警,触发阈值是使用率超过 85%。
5.1 第一步:确认是不是真的,别急着动手
告警可能是采集抖动,也可能是监控取错了指标。先做三件事:确认使用率、看Threads_running、看是不是正在快速增长。
SELECT (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Threads_connected') AS conn, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Threads_running') AS run, (SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME='max_connections') AS max_conn, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Max_used_connections') AS peak;拿到结果后分三种情况:
conn接近max_conn,run也高 → 真的压力大,优先做限流和慢 SQL 处置。conn接近max_conn,run个位数 → 泄漏或池子配置问题,重启应用能立刻缓解,但要追根因。conn中等,但peak很高 → 说明历史上有过尖峰,当前正常,看告警时间点对不对得上。
这一步的意义在于避免误判方向。我见过不止一次,值班的人一看告警直接去改max_connections,结果连“是不是真有压力”都没确认,改完参数问题依旧。
5.2 第二步:揪出占连接的来源
SELECT SUBSTRING_INDEX(HOST, ':', 1) AS ip, USER, COUNT(*) AS total, SUM(COMMAND = 'Sleep') AS sleeping, SUM(COMMAND != 'Sleep') AS active, MAX(TIME) AS max_time FROM information_schema.PROCESSLIST GROUP BY ip, USER ORDER BY total DESC;按 IP 聚合通常能立刻看出是哪台机器占得多。如果某个 IP 占了七成连接,那问题就在那个应用实例上,和数据库本身关系不大。再看sleeping与active的比例:sleeping 占绝大多数,就是池子或者泄漏;active 占多数,就是这台机器真的在打压力。
接着看这个 IP 的TIME最大值。如果有一批连接Sleep了几千秒,那就是池子里配了大量长期闲置连接。这时去对应用那边看maximumPoolSize和minimumIdle,基本能对上号。
5.3 第三步:处置的优先级排序
处置手段按“副作用从小到大”排:
- 限流 / 摘流量。影响面可控,见效快。先在入口把流量降下来,给后面争取时间。
- 终止明确的阻塞源。比如那个开了事务不提交的连接,或者跑了半小时的报表查询。目标明确,副作用可预期。
- 调整
wait_timeout让闲置连接自然回收。注意这是动态参数,SET GLOBAL只影响新连接,已有连接还是旧值,所以见效慢。 - 临时调高
max_connections。这是最后手段,因为它只是把墙往后挪,没解决任何问题,还可能让内存风险上升。真要调,配合“同时收紧超时参数”一起做。 - 重启应用。对泄漏类问题立竿见影,但根因还在,几个小时后会重演。作为止血手段可以,作为解决方案不行。
我个人的偏好顺序是先做 2 再做 1,因为在阻塞类场景里,掐掉阻塞源往往能让整个链路瞬间恢复,比限流更彻底。但前提是你能确定阻塞源——不确定的话,先限流保命。
5.4 max_connections 到底设多少:一次参数计算演示
假设场景:应用实例 8 台,每台连接池maximumPoolSize设 20,另有一个报表服务和两个批处理任务,各用 10 个连接。历史峰值Max_used_connections是 210。
计算过程:
- 应用层理论最大连接数:8 × 20 = 160
- 报表与批处理:10 + 10 + 10 = 30
- 运维与监控账号预留:10
- 合计约 200,历史峰值 210,说明实际使用已经贴近理论上限
那么max_connections设多少合适?我的取法是在峰值上留 30% 余量,再取整:210 × 1.3 ≈ 273,取 300。
内存侧校验:单连接按 1.2MB 估算,300 × 1.2MB ≈ 360MB,加上全局 buffer pool 和其他开销,在预期范围内。如果这台机器 buffer pool 已经占了 32GB、机器总内存 64GB,那 360MB 完全能接受。
如果环境里sort_buffer_size被上调到了 4MB,重新估算就是单连接 4MB 以上,300 连接就是 1.2GB 起步,这时候要么降参数,要么降连接数上限,二选一,不能都放着不管。
注意:
max_connections用SET GLOBAL改是即时生效的,但重启后会丢,必须同时写进配置文件。这一点在应急处置时容易忘,第二天重启又回到原值,然后同样的告警再来一遍。
6. 常见问题速查与几条踩坑心得
6.1 连接数相关故障速查表
| 现象 | 大概率原因 | 快速验证 | 处置方向 |
|---|---|---|---|
| 报 Too many connections | 连接数真打满 | 看Threads_connected与max_connections | 先限流,再定位占用源 |
| 连接数高但 CPU 很低 | 闲置连接堆积或泄漏 | Threads_running个位数,Sleep占多数 | 查池子配置,查代码释放 |
| 偶发 Communications link failure | 池子 lifetime 大于服务端超时 | 比对maxLifetime与wait_timeout | 调小池子 lifetime |
Threads_created持续增长 | 连接不复用 | 看是否走连接池、wait_timeout是否过短 | 上池子,调超时 |
| 大量 Waiting for table metadata lock | 长事务未提交或 DDL 排队 | 查INNODB_TRX里长事务 | 终止事务,避开高峰做 DDL |
Aborted_connects持续增长 | 认证失败、host 不匹配、连接数满 | 查错误日志、核对账号授权 | 补授权、改配置 |
| 连接建立成功但立刻断开 | max_connect_errors触发封禁 | 查该指标、看错误日志 | FLUSH HOSTS解封并修根因 |
6.2 应用侧连接池的几组参数关系必须对齐
数据库侧的调优只是半张牌,另一半在应用侧。几组关系务必对齐:
maxLifetime<wait_timeout,且要小于任何中间层的空闲超时。maximumPoolSize× 实例数不能超过数据库max_connections的七成,要给别的服务和人工留口子。minimumIdle不要设得接近maximumPoolSize,否则池子一直保持大量空闲连接,白白占资源。connectionTimeout要小于上游接口超时,让连接获取失败能在业务超时之前暴露出来,而不是全部堆在最后一起爆。
这几条看着像常识,但我在不同项目里反复见到反例。最常见的是“池子上限按单机 100 配,实例扩容到 20 台”,数据库那边还是 500 上下的max_connections,一扩容就把库顶死。扩容前先算一遍总连接数,这个动作应该写进发布检查单。
6.3 几条实操心得
不要迷信“连接数少就是好”。有些团队把连接池调到很小,结果高峰期请求全卡在等连接上,接口延迟飙升但数据库毫无压力。连接数要和实际并发匹配,过少和过多都是问题,判断依据是Threads_running和业务延迟,不是单看Threads_connected。
排查前先看时间线,别只看看快照。连接数问题的价值全在趋势里。一条单调上升的曲线和一条周期波动的曲线,处理方式完全不同。我习惯在告警触发时先拉最近 6 小时的Threads_connected曲线,一分钟就能判断是渐变型还是脉冲型。
SHOW PROCESSLIST里看到自己。排查时经常会看到自己这条排查用的连接也在列表里,别把它算进业务连接。用CONNECTION_ID()可以确认自己的 id,写筛选 SQL 时排除掉,数字会更干净。
长事务的根因往往不在数据库。九成以上的长事务是应用侧忘了提交、或者把查询包在一个大事务里做了大量业务逻辑。修数据库参数治不了这个,得回去看代码里事务边界划定得对不对。
给监控加一条“连接使用率”的自定义指标。官方的连接数监控通常只有绝对值,看不到容量关系。自己算一个Threads_connected / max_connections的比值,阈值设 0.7 预警、0.85 严重,比盯绝对值直观得多,尤其是在不同规格的实例上统一告警标准时特别有用。
预留一个专用管理账号。高权限、只允许从跳板机登录、不参与业务。连接数打满的时候,这个账号是唯一的救命通道。这个习惯平时看不出来价值,真出事那天能省下半小时。
最后分享一个我一直在用的小脚本思路:定时采一次连接相关指标,写进时序库,同时把Threads_connected排名前五的来源 IP 记录下来。事后复盘时,能直接看出是哪个服务在某个时间点开始涨,比事后去翻日志快太多。这套东西搭起来不到一百行,收益却远超预期——连接数这类慢性问题,最有价值的从来不是当下的处置,而是能不能提前三天看出来。