编程 RDS MySQL 连接数打满连不上:先分空闲会话与活跃堆积两条线

2026-10-25 00:03:50

RDS MySQL 连接数打满连不上:先分空闲会话与活跃堆积两条线

官方文档:解决 RDS MySQL 连接数满导致的无法连接
关联文档:RDS MySQL 活跃线程数高问题

现象

业务侧开始成片报错,客户端拿不到连接:

ERROR 1040 (HY000): Too many connections

不是密码错、不是网络不通,是实例的连接槽位被占满了。连 DMS 都进不去时,问题已经从"某个应用连不上"升级成"整实例无法管理"。

为什么连接数是规格上限,而不是一个参数

RDS MySQL 的连接数上限跟实例规格走,不是靠调某个参数就能突破的开关。连接池、DMS、运维脚本、定时任务共用这同一份额度,任何一方超量占用,其余方就排队。所以真正的问题不是"能不能多给点连接",而是"这些连接都在干什么"。

定位:先看是空闲还是活跃

进 RDS 控制台 云数据库 RDS / 实例列表 / 监控与报警 页面,找到会话连接监控项,看 mysql.active_session 的走势:它代表活跃连接数,和总连接数对比就能分型。

  • 总连接数高、mysql.active_session 低 → 空闲连接堆积,绝大多数会话在 sleep。
  • 总连接数和 mysql.active_session 一起高 → 活跃连接堆积,SQL 在跑或在等。

能连上时用 show processlist 直接看会话状态;threads_connected 是当前已建立的连接总数,用来看增长趋势。

空闲连接三型

1. 长连接池初始值过大

典型是 Java 应用。应用启动后连接池立刻建出一批到 RDS 的连接,之后应用没有这么大并发,连接就一直 sleep 在那里占着槽位。

2. 短连接没有 close

典型是 PHP 应用。大量空闲连接说明查询执行完后代码没有显式关闭连接,连接对象一直挂到超时。

3. 连接带了 CLIENT_INTERACTIVE,走的是另一套超时

应用默认是非交互模式,受 wait_timeout 管;PHP 把 MYSQL_CLIENT_INTERACTIVE 常量传给 mysql_connect() 后即为交互模式,这类会话只受 interactive_timeout 管。只调 wait_timeout 对它们无效,会话照样挂着。

对应处理:

  • 空闲会话通过控制台会话管理、DMS 或 kill 命令终止。
  • 长连接模式启用连接池复用,并开启连接检测。
  • 短连接模式在查询结束后调用关闭连接的方法。
  • 非交互连接:控制台把 wait_timeout 调小。
  • 交互连接:控制台把 interactive_timeout 调小。

两个超时参数的差别与生效边界

参数作用对象默认值
wait_timeout非交互连接86400 秒(24 小时)
interactive_timeout交互连接(CLIENT_INTERACTIVE)7200 秒(2 小时)

单位都是秒,空闲超过阈值后由 RDS 主动关闭连接。

关键边界:这两个参数修改后,只对修改之后新建的会话生效。修改之前已经存在的会话继续沿用原设置。所以把参数调小之后,存量空闲连接不会立刻消失,得等它们自己撞到原有阈值。

参数调到多少合适,取决于业务的连接复用能力和可接受的建连开销,不要一次调到极小值让长连接池反复重建。

活跃堆积四型

活跃连接多,说明会话不是在 sleep,是在执行或等待。

慢 SQL 堆积:健康实例的活跃连接数通常低于 10,高规格高 QPS 场景可能在 20~30;出现几百上千基本可以判定 SQL 在堆积。用 show processlist 看具体语句。

MDL 锁:表现为大量 Waiting for table metadata lock。DDL 在 prepare 和 commit 阶段需要 MDL 锁,若表上有未提交事务或慢 SQL,DDL 被阻塞,后续访问该表的 SQL 继续级联阻塞。

InnoDB 行锁等待:看 innodb_row_lock_waits、innodb_row_lock_time,再用 show engine innodb status 查看 Lock wait 段落定位持锁事务。

CPU / IOPS 饱和:CPU 使用率过高、IOPS 使用率高,会话处理变慢,活跃连接自然堆上去。另外 Table Cache 不足时会出现大量 Opening tables 状态,需要调 table_open_cache。

打满时怎么自救

连接数完全打满时,DMS 或其他方式也连不上实例,常规 SQL 手段全部失效,只能从控制台动:

  1. 在控制台参数设置里把 wait_timeout 改小,促使实例主动关闭空闲超过阈值的连接,腾出槽位。
  2. 通过控制台会话管理或 DMS 终止空闲会话。
  3. 恢复连接能力后,再按上面的分型定位根因。

预防上,长连接模式的连接池最大连接数要比实例规格的连接数限制略小,建议保留 10 个连接给 DMS 或其他管理操作,避免管理通道被业务连接挤死。

另外两件会影响连接的事:实例升配过程中可能有一次 30 秒左右闪断,业务要做重连;重启实例约 30 秒连接中断,重启还会释放 Buffer Pool,冷启动后一段时间内的查询表现会变化。

应用侧配置片段

Go 的 sql.DB,按规格上限留出管理余量:

// maxConns 为实例规格的连接数限制
db.SetMaxOpenConns(maxConns - 10) // 预留 10 个给 DMS 和运维操作
db.SetMaxIdleConns(maxConns - 10) // 空闲连接数不要超过上限,避免空占槽位

PHP PDO,短连接模式下确认查询结束后连接被释放:

$pdo = new PDO($dsn, $user, $pass, [
    PDO::ATTR_PERSISTENT => false, // 短连接模式
]);

$stmt = $pdo->query($sql);
// ... 处理结果
$stmt = null;
$pdo  = null; // 显式释放,不要依赖请求结束或超时回收

若使用持久连接(长连接),连接池侧要控制好复用与初始连接数,并对拿到的连接做检测,避免复用到已被服务端关闭的会话。

结论

连接数打满先分型再动手:mysql.active_session 低、总连接高就是空闲连接,查连接池初始值、短连接是否 close、会话是否带 CLIENT_INTERACTIVE;两者一起高就是活跃堆积,查慢 SQL、MDL、行锁和 CPU/IOPS。调超时参数记住默认值和生效边界——wait_timeout 默认 86400、interactive_timeout 默认 7200,且只对新会话生效。连接池上限永远比规格少留 10 个。

推荐文章

程序员茄子在线接单