网站突然报 ERROR 1040 (HY000): Too many connections,API 无法访问数据库,连管理工具也登录不上?这表示 MySQL 可供普通客户端使用的连接已经被占满。它不一定是 VPS 性能不够,也可能是部署后进程数增加、连接池配置放大、连接没有归还,或慢查询让请求越积越多。
本文以 MySQL 8.4 为主要参考,先恢复管理入口,再定位连接占用,最后调整应用连接池与数据库容量。MySQL 8.0 可核对对应版本文档;MariaDB 的权限、系统表和持久化配置存在差异,不要直接照搬下面的 MySQL 专用语句。
| 报错或现象 | 对应方向 |
|---|---|
MySQL 1040 Too many connections | 数据库普通连接额度耗尽 |
Too many user connections 或账号资源限制错误 | 单账号连接额度,不等于全局 max_connections 已满 |
Access denied、1045 | 账号、密码或认证匹配 |
2003、Connection refused、连接超时 | 服务、地址、监听或网络 |
应用 QueuePool limit ... timeout 等 | 应用连接池等待超时,数据库连接总数未必已满 |
不要只根据“数据库连接不上”就把 max_connections 调高。本文处理的是服务器返回的连接额度错误;网络和登录失败另见 MySQL / MariaDB 连接与启动排查。MySQL 对 1040 场景的说明见官方 Too many connections 文档。
故障时优先暂停能够确认的非必要任务,例如批量导入、报表、队列消费者或重复触发的定时任务,并降低应用并发。对持续失败的连接请求设置有上限的重试和退避,避免每个请求立即重试,把数据库拖进更大的连接风暴。
保留已经登录的管理会话,不要同时打开很多新管理窗口。使用已有管理员账号从 VPS 本机连接,密码在提示时输入:
mysql --connect-timeout=5 -u db_admin -p
这里的 db_admin 只是示例,请替换成实际账号。本地 socket、主机地址和认证方式也要与安装一致;不是所有系统都能通过 sudo mysql 无密码登录。Docker Compose 部署可进入现有 MySQL 服务,例如:
docker compose exec mysql mysql --connect-timeout=5 -u root -p
将 mysql 服务名替换为现有项目的真实服务。通过容器执行客户端不会绕过数据库连接限制,只是避免把网络问题和连接额度问题混在一起。
MySQL 通常在普通额度之外保留一个连接供具有 CONNECTION_ADMIN 权限的账号使用;它不是每个管理员都各有一个槽位。如果该保留连接也已占用,就不能指望再开一个窗口一定成功。相关权限应该提前给专用管理账号配置,不能给业务账号以此绕过限额。专用管理接口也需要提前配置,不能把普通 3306 端口临时开放到公网来获得额外容量。
如果仍然无法管理,继续减少已确认的应用流量或有问题的应用进程,让连接自然释放。仅在确认业务影响、保留现场并安排恢复步骤后,才考虑数据库重启;重启会中断全部连接、丢失部分现场信息,而且应用一齐重连可能让故障立即复发。
登录后先确认正在排查正确的实例:
SELECT VERSION(), @@hostname, @@port, CURRENT_USER(), CONNECTION_ID();
SHOW GLOBAL VARIABLES
WHERE Variable_name IN (
'max_connections', 'max_user_connections',
'wait_timeout', 'interactive_timeout'
);
SHOW GLOBAL STATUS
WHERE Variable_name IN (
'Threads_connected', 'Threads_running',
'Max_used_connections', 'Connection_errors_max_connections',
'Connections', 'Uptime'
);
用已有管理会话间隔采样,观察变化,不要每次采样都创建一个新连接:
Threads_connected是当前打开的连接数量。Threads_running是非休眠线程数量,不能直接当作 CPU 使用率。Max_used_connections是连接高水位,不是此刻的连接数。Connection_errors_max_connections记录因达到连接上限而拒绝连接的情况,应看观察区间内是否继续增加。Connections是连接尝试的累计计数,需要结合时间差理解,不能把总值当作每秒连接数。
重启或统计重置会改变比较基准,所以记录时间和 Uptime。如果你进入时连接已经回落,不能只凭一次正常快照否认之前的 1040。各指标定义见 MySQL 状态变量参考。
MySQL 8.4 可以直接查询 Performance Schema 的连接列表。先按账号和状态汇总,再查看较长时间的会话:
SELECT USER, COMMAND, COUNT(*) AS connection_count,
MAX(TIME) AS longest_state_seconds
FROM performance_schema.processlist
GROUP BY USER, COMMAND
ORDER BY connection_count DESC;
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE
FROM performance_schema.processlist
ORDER BY TIME DESC
LIMIT 50;
结合账号、来源地址、应用日志和部署时间定位服务。HOST 通常包含客户端端口,来自同一应用的连接也可能显示不同端口。不要把一个端口对应成一套独立应用,也不要仅按 TIME 排序就认为第一条一定是故障根源。
查看其他用户的会话通常需要相应权限;没有权限、监控表不可用或监控配置不完整时,列表不完整并不代表其他应用没有连接。MySQL 的表结构与可见性说明见 processlist 表文档。
Sleep 表示会话当前没有执行语句。连接池会保留空闲连接以供复用,所以它本身不是泄漏证据。更有价值的判断是:
- 连接数量是否符合配置的实例数、进程数与池容量。
- 业务停止产生请求后,连接能否归还到池中、复用或按设计回收。
- 是否在每个请求、定时任务或异常路径里新建连接池。
- 是否有连接被长期借出未归还,或事务开启后一直未提交/回滚。
休眠会话也可能仍持有事务。可查看当前 InnoDB 事务:
SELECT trx_mysql_thread_id, trx_started, trx_state
FROM information_schema.innodb_trx
ORDER BY trx_started;
将 trx_mysql_thread_id 与连接 ID 对照。事务存在不等于可以随便终止,要结合业务与锁等待继续判断。长事务占用连接、阻塞其他请求,会让连接数问题与数据库延迟互相放大。
先限制产生问题连接的应用,再决定是否终止具体会话。以下连接 ID 是示例,必须替换为刚刚核实的目标:
KILL CONNECTION 12345;
KILL CONNECTION 会关闭该连接并影响其正在执行的工作;未提交事务可能回滚,回滚也可能需要时间。KILL QUERY 只终止当前语句并保留连接,所以它不保证释放连接额度。权限与行为见 MySQL KILL 文档。
不要根据“Sleep 超过多少秒”批量杀掉所有连接,也不要误杀复制、备份、管理会话或正常连接池。若应用不停立即重建连接,单独 KILL 只是短暂清空列表,无法消除连接来源的问题。
常见误区是:应用配置里写了 pool_size=10,就以为整套服务最多占 10 个连接。实际部署可能有多个副本、每个副本多个进程,每个进程各自拥有连接池;部分连接池还有额外溢出连接。
对于这类每进程独立池,粗略容量预算是:
应用实例数 × 每实例进程数 × 每进程最大池容量,再加队列任务、定时任务、管理与其他服务。
以有界 SQLAlchemy QueuePool 为例,最大可同时借出的连接与 pool_size + max_overflow 有关,具体行为见连接池官方文档。下面是一个假设预算,不是任何网站的实测数据:
| 来源 | 潜在连接上限 |
|---|---|
| API:2 个实例 × 4 个进程 ×(10 个池连接 + 10 个 overflow) | 160 |
| 后台任务 | 20 |
| 其他站点与定时任务 | 10 |
| 管理和监控预留 | 10 |
| 合计 | 200 |
如果数据库普通连接上限是 151,这套配置有触顶的可能。预算是潜在上限,并不意味着所有池启动时就立即创建全部连接;应再用观测数据核对。
优先根据请求并发与数据库处理能力缩小不必要的池容量、overflow 和后台并发,保留管理余量。检查连接是否在请求结束、异常和取消路径中正确释放,事务是否按时提交或回滚,连接池是否在合适的进程生命周期中初始化。
扩容应用副本、增加 Web worker 或滚动发布时,重新计算预算。新旧实例短暂共存,会使发布窗口的连接峰值高于平时;所有实例同时预热或重启也可能制造连接突发。
当连接额度确实不足、应用连接使用合理,而且 VPS 的内存与数据库处理能力仍有余量时,可以评估适度增加。max_connections 在 MySQL 8.4 是全局动态变量,实际生效值还会受系统资源等条件影响,见系统变量参考。
先在 VPS 查看内存;容器部署还要看容器限额:
free -h
vmstat 1 5
docker stats --no-stream
只在使用 Docker 时运行最后一条。不能只看宿主机剩余内存,因为 MySQL 容器可能有更低的硬限制。每个连接的内存并不是一个固定常数,查询缓冲、临时表与其他开销会随负载变化;把上限直接设成 1000,可能让 1040 变成 OOM。低内存相关排查见 MySQL Docker OOM 与资源调优。
下面的 200 只是变更语法示例。应替换为按实际预算和容量评估得到的值,并先记录原值:
SET @incident_old_max_connections = @@GLOBAL.max_connections;
SELECT @incident_old_max_connections;
SET GLOBAL max_connections = 200;
SELECT @@GLOBAL.max_connections;
需要足够的管理权限。SET GLOBAL 修改当前运行值,不要求数据库重启,但通常不会让该设置跨重启保存。不要在故障时一边调整限额,一边同时修改多个内存参数,否则难以判断效果。
同一管理会话中,若评估发现需要撤回:
SET GLOBAL max_connections = @incident_old_max_connections;
这个 @incident_old_max_connections 是会话变量,断开后会丢失。因此也要把查询出的原数值记录到变更记录;重新登录后应明确使用记录值恢复。降低上限不能当作清理现有会话的手段,还应评估之后新连接的可用余量。
确认应用连接池已经受控、指标与业务都稳定后,再选择一种明确的配置管理方式。MySQL 支持用 SET PERSIST 同时修改运行值并保存到持久化配置:
SET PERSIST max_connections = 200;
仍应替换示例值。语义与权限见 SET 语法文档。如果需要撤销由它保存的这项配置,可使用:
RESET PERSIST max_connections;
RESET PERSIST 删除持久化设置,但不恢复当前运行值。 运行值要另行按原记录恢复。语义见 RESET PERSIST 文档。也可以按原项目流程维护 MySQL 配置文件,但应核对实际加载来源,避免 my.cnf、持久化值和部署参数留下互相矛盾的记录。
Docker 中重建容器不一定清掉数据目录内的持久化设置;不要删除数据库 Volume 来“重置连接数”。配置、数据和备份管理可参考 MySQL 8.4 Docker 部署教程。
不是。wait_timeout 用于空闲连接超时,不会替你终止正在工作的慢查询;把它调得很小可能让连接池复用到已被服务器关闭的连接,转而出现断线错误。全局设置与已有会话的实际超时也要区分。
应把服务器空闲超时与连接池的回收、存活检查和业务长事务需求一起设计。先修复未归还连接与错误的池生命周期,再根据实际空闲周期调整;不要把统一的 30 秒或 60 秒当作所有 VPS 的最佳值。
用原来的应用账号和连接路径验证登录、查询、写入及关键事务,再逐步恢复暂停的任务。观察一个能覆盖正常流量与任务运行的区间:
Threads_connected是否在预算范围内,而不是持续爬升。- 1040 与
Connection_errors_max_connections是否继续增加。 - 查询延迟、连接池等待、错误率和内存是否正常。
- 发布、队列高峰和定时任务同时运行时,是否仍有连接余量。
一次成功登录或重启后连接归零,都不算根因修复。保留故障前后的快照、原参数、应用池配置与回滚记录,下一次扩容应用时也能重新计算数据库预算。
处理 1040 的顺序是:控制新连接 → 找到占用来源 → 修复连接池或长时间占用 → 评估数据库容量。限额确实不够时再调整,才能避免故障反复出现。
