网站隔三差五报一句数据库连接失败,刷新两下又好了。这种时好时坏的毛病最烦人,因为它不像宕机那样干脆,你没法守在屏幕前等它出现。等到真去查的时候,现场早没了。
MySQL 从 5.7 起 max_connections 默认就是 151,8.0 也沿用了这个值。超过之后新连接直接被拒,报 ERROR 1040。看到这个错,第一反应通常是把上限调大。我劝你先别急,因为多数情况下这不是容量问题,是连接泄漏。
怎么区分?看三条状态。Max_used_connections 是启动以来的峰值,如果它离上限还差得远就报了 1040,说明是瞬时尖峰;Threads_connected 是当前连接数;Connection_errors_max_connections 是被拒绝过的次数,这个数字只要不是 0,就说明上限确实被碰到了。
真正能定性的是 processlist 里的状态分布。如果大部分连接都挂在 Sleep 上,那就是泄漏——程序拿了连接不还,连接闲着占内存,还把名额占满了。这种情况你把上限调到 1000 也没用,只是把崩的时间往后推,等内存吃干抹净,OOM 就来了。
每个连接都要吃掉一块内存,来自 sort_buffer、join_buffer、read_buffer、read_rnd_buffer、thread_stack 这几个按连接分配的缓冲区。按默认配置算,一个连接大约 2 到 4 MB。把上限从 151 提到 1000,光连接本身就要多备两三个 G,这笔账得先算清楚。
-- 连接数体检:先看状态,再决定要不要调上限
-- 1. 上限、峰值、当前值、被拒次数,四条一起看
SHOW VARIABLES LIKE 'max_connections';
SHOW STATUS LIKE 'Max_used_connections';
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Connection_errors_max_connections';
-- 2. 连接都在干什么:Sleep 多就是泄漏,Query 多才是真忙
SELECT command, count(*) AS cnt,
max(time) AS max_time_sec
FROM information_schema.processlist
GROUP BY command ORDER BY cnt DESC;
-- 3. Sleep 超过 60 秒的连接,是重点怀疑对象
SELECT id, user, host, db, time, state
FROM information_schema.processlist
WHERE command = 'Sleep' AND time > 60
ORDER BY time DESC LIMIT 20;
-- 4. 单个连接的理论内存开销(MB)
SELECT ROUND((
@@read_buffer_size + @@read_rnd_buffer_size + @@sort_buffer_size
+ @@join_buffer_size + @@binlog_cache_size + @@thread_stack
) / 1024 / 1024, 2) AS per_connection_mb;
确认是泄漏之后,处置方向就明确了:让空闲连接早点释放。wait_timeout 管非交互连接、interactive_timeout 管交互连接,默认都是 28800 秒也就是 8 小时,对 Web 应用来说长得离谱。改成 300 秒,空闲五分钟的连接自动断开,名额就回来了。
但这是治标。真正的病根在程序里——查询完没关连接、异常分支漏了释放、异步任务里拿了连接没还。改 wait_timeout 只是让泄漏的速度赶不上回收的速度,程序该修还得修。顺便说一句,长连接不是不能用,但要配合连接池,让池来控制总量,而不是让每个请求自己去连。
真到了业务确实需要更多连接的那天,再按内存算上限:可用内存减去 InnoDB 缓冲池、减去系统开销,剩下的除以单连接内存。算出来多少就是多少,别填整数也别往上凑。改完记得同步检查操作系统的文件描述符限制,那个数不够的话 MySQL 起不来。
数据来源:MySQL 8.0 官方参考手册
申请创业报道,分享创业好点子。点击此处,共同探讨创业新机遇!
