MySQL 连接数打满的定位方法:谁在占着连接

ERROR 1040 (HY000): Too many connections 这个错误,直接调大 max_connections 通常只是把问题推后。真正的解法是先搞清谁在占连接、占着在干什么。

三种完全不同的占满类型,解法相反:

类型特征根因解法
连接泄漏连接数缓慢单调上升,不回落代码拿了连接没释放修代码;调大只是拖延
瞬时高峰连接数尖峰后回落流量突增或批量任务限流、连接池、错峰执行
长连接堆积部分连接长期 Sleep 但 Time 很大应用配了超长空闲时间调小 wait_timeout 或应用侧回收

第一步:确认是哪种类型

-- 当前连接数与历史峰值
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Max_used_connections';
SHOW VARIABLES LIKE 'max_connections';

-- 采样两次看趋势(间隔 30 秒)
SHOW STATUS LIKE 'Threads_connected';
-- sleep 30 后再查一次

-- 被拒绝的连接次数(累加说明反复打满)
SHOW STATUS LIKE 'Aborted_connects';
  • 两次采样持续上升 = 连接泄漏(最需要改代码)
  • 两次采样波动但峰值高 = 瞬时高峰
  • Aborted_connects 持续增长 = 客户端连不上(配置或网络问题,不是连接数问题)
  • Max_used / max_connections 长期 > 0.8 = 处于危险水位

第二步:看 processlist(关键是 Command 与 Time)

-- 只看非 Sleep 的连接,按时间倒序
SELECT id, user, host, db, command, time, state,
       LEFT(info, 80) AS query
FROM information_schema.processlist
WHERE command <> 'Sleep'
ORDER BY time DESC
LIMIT 20;

-- 先看状态分布
SELECT command, state, COUNT(*) AS n
FROM information_schema.processlist
GROUP BY command, state
ORDER BY n DESC;

-- 按来源 IP 分组(找出哪个实例占最多)
SELECT SUBSTRING_INDEX(host, ':', 1) AS client, COUNT(*) AS n
FROM information_schema.processlist
GROUP BY client ORDER BY n DESC;
Command含义要不要担心
Query正在执行 SQL要担心——看 time 是否很长
Sleep空闲等命令看 time 是否特别长(可能泄漏)
Locked等表锁要担心——有锁竞争
Waiting for table metadata lock等元数据锁非常要担心——长事务或未提交的 DDL
Copying to tmp table在写临时表查询太重,缺索引
Sending data在发数据可能是大结果集
Killed被杀但没退出等它退出,必要时 kill

第三步:连接泄漏的三种典型代码

类型 1:忘了 close

// 错误:拿到 rows 后若某分支提前 return 而没 defer,就泄漏
rows, err := db.Query("SELECT ...")
if err != nil {
    return err
}
// 若这里中间有 return,连接不释放
defer rows.Close()

// 正确:紧跟着 defer
rows, err := db.Query("SELECT ...")
if err != nil {
    return err
}
defer rows.Close()
for rows.Next() { ... }

类型 2:事务没提交也没回滚

// 错误:出错时直接 return,事务没回滚,连接带着未提交事务被占住
tx, _ := db.Begin()
_, err = tx.Exec("UPDATE ...")
if err != nil {
    return err        // ← 事务没回滚!
}
tx.Commit()

// 正确:defer 兜底(已 Commit 时再 Rollback 是空操作)
tx, err := db.Begin()
if err != nil {
    return err
}
defer tx.Rollback()
if _, err = tx.Exec("UPDATE ..."); err != nil {
    return err
}
return tx.Commit()

类型 3:连接池参数配错

db, _ := sql.Open("mysql", dsn)
db.SetMaxOpenConns(20)                        // 默认 0 = 无限!
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(time.Hour)              // 强制回收,防中间设备断连留死连接
db.SetConnMaxIdleTime(10 * time.Minute)
  • SetMaxOpenConns 默认 0 表示无限制——这是最容易踩的坑,高并发时开出几百连接把 MySQL 打满
  • SetConnMaxLifetime 必须设:不设的话,中间防火墙断掉的死连接会一直留在池里
  • MaxOpenConns 不是越大越好:超过 MySQL 的 max_connections 就直接报 1040
  • 经验值:单实例 MaxOpenConns ≈ max_connections / 应用实例数 × 0.7

第四步:瞬时高峰怎么判断

-- 按秒看查询峰值(需 performance_schema)
SELECT DATE_FORMAT(event_time,'%H:%i:%s') AS sec, COUNT(*) AS n
FROM performance_schema.events_statements_history
GROUP BY sec ORDER BY n DESC LIMIT 10;

-- 看谁在短时间内发了大量查询
SELECT user, COUNT(*) AS queries
FROM performance_schema.events_statements_history
WHERE event_time > NOW() - INTERVAL 1 MINUTE
GROUP BY user ORDER BY queries DESC;

瞬时高峰的典型来源:

  • 定时任务叠加:多个 cron 同一分钟触发(备份 + 报表 + 清理),各自开连接
  • 批量接口:一次请求开 N 个并发查询
  • 缓存集体失效:缓存过期瞬间大量请求同时打 DB
  • 慢查询堆积:一个慢查询占住连接,后续请求排队,连接数越堆越高

第五步:除了调 max_connections 更该做什么

调大 max_connections 有硬上限(受 open_files_limit 与内存限制)。每个连接占线程栈约 256KB-1MB,调太大会 OOM。

-- 检查现有上限
SHOW VARIABLES LIKE 'max_connections';
SHOW VARIABLES LIKE 'open_files_limit';
-- 系统侧
1. ulimit -n

-- 临时调(重启失效,用于止血)
SET GLOBAL max_connections = 300;

-- 永久调:my.cnf [mysqld] max_connections=300
-- 同时确认 open_files_limit 足够(每个连接约 2 个 fd)

但更根本的四件事:

措施解决的问题优先级
应用侧设 MaxOpenConns 上限把「无限开连接」变成「有界」最高
加索引消除慢查询缩短慢查询占住连接的时间最高
设 wait_timeout 回收空闲连接Sleep 连接堆积高
读写分离 / 加缓存减少打到主库的查询量中
连接池中间件(ProxySQL)多实例统一管连接按需
-- 回收长期空闲连接
SHOW VARIABLES LIKE 'wait_timeout';   -- 默认 28800 秒(8 小时)
SET GLOBAL wait_timeout = 600;        -- 改为 10 分钟

-- 找出长时间 Sleep 的(先确认不是应用连接池里的)
SELECT id, user, host, time FROM information_schema.processlist
WHERE command='Sleep' AND time > 3600;

-- 确认后可 KILL <id>;

wait_timeout 设太小有副作用:应用连接池里的空闲连接会被 MySQL 单方面断开,应用再拿来用就报错。要么应用侧配 ConnMaxLifetime 小于 wait_timeout,要么就别乱调。

第六步:应急止血 vs 根因修复

场景应急(5 分钟内)根因
连接数瞬间打满、站点 502调大 max_connections 到 300,或 kill 最长 Sleep查慢查询日志
持续泄漏、越用越满重启应用进程(释放所有连接)修连接未关的代码
锁等待导致全堵找出元数据锁持有者并 kill查未提交事务 / 长 DDL
定时任务并发临时错开执行时间重排调度或改队列

总结

  • 先分清三种类型:泄漏(单调上升)、高峰(尖峰回落)、堆积(长 Sleep),解法完全相反
  • processlist 看 Command 与 Time,别只看数量——20 个 Query 比 200 个 Sleep 更危险
  • Go/Java 的池子默认常常无上限,SetMaxOpenConns 不设等于给 MySQL 埋雷
  • 调大 max_connections 有内存天花板:每连接 256KB-1MB 栈,300 连接可能吃掉几百 MB
  • 根治靠三件事:连接数上限、消除慢查询、合理回收空闲
暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇