长连接不释放会导致内存持续上涨,因mysql线程内存不随语句结束立即释放,长期复用且无清理会引发oom;wait_timeout等参数对心跳连接无效,需应用层连接池主动控制最大存活时间和执行语句数。

长连接不释放会导致内存持续上涨
MySQL 的长连接本身不会自动释放分配的内存,尤其是当执行过大量临时表、排序、大结果集查询后,线程内部的内存(如 sort_buffer_size、tmp_table_size)不会随语句结束立即归还给系统,而是保留在连接生命周期内。如果应用层长期复用同一个连接且不做清理,内存占用会缓慢爬升,最终触发 OOM 或被 OS 杀掉进程。
- 典型现象:
SHOW PROCESSLIST中大量Sleep状态连接,但mysqladmin ext -i1 | grep Threads_created持续上升,或top中mysqldRSS 不断增长 - 不是连接数多才出问题——10 个长连接跑一周,可能比 100 个短连接每秒新建更耗内存
- MySQL 5.7+ 对线程内存回收有优化,但无法完全避免累积,尤其在启用了
query_cache_type=1(已弃用)或大量PREPARE语句时
wait_timeout 和 interactive_timeout 不是万能解药
这两个参数控制空闲连接自动断开时间,但它们只对“无任何命令交互”的连接生效。只要应用层定期发心跳(比如 PING 或空 SELECT 1),连接就永远不会超时,内存也就一直挂着。
-
wait_timeout默认 28800 秒(8 小时),但业务连接池常设为 0(永不过期)或极大值,等于关掉了这道闸 - 即使设为 300 秒,若连接池每 299 秒发一次
SELECT 1,照样无效 - 注意:该超时由 MySQL server 单方面触发,客户端不会收到优雅通知,下次使用可能直接报
Lost connection to MySQL server during query
应用层必须主动控制连接生命周期
靠数据库侧被动回收不可靠,真正有效的策略是让连接池自己管理“最大存活时间”和“最大执行语句数”,强制轮换连接。
- Java HikariCP 推荐配
maxLifetime=1800000(30 分钟) +idleTimeout=600000(10 分钟),避免连接活太久 - Python PyMySQL / mysql-connector-python 可在每次获取连接后检查
connection.connection_id,配合计数器限制单连接执行语句不超过 1000 条(防慢 SQL 积累) - Node.js mysql2 支持
connectionLimit和acquireTimeout,但需手动加connection.on('end', ...)做清理钩子,否则异常断连后连接不会从池中剔除 - 切忌在事务中复用连接做长时间轮询——一个未提交事务会让整个连接内存锁死,直到 commit/rollback
监控与验证是否真在泄漏
别只看连接数或 Threads_connected,要定位到具体连接的内存占用来源。
- 查每个线程内存:MySQL 8.0+ 执行
SELECT * FROM performance_schema.memory_summary_by_thread_by_event_name WHERE THREAD_ID IN (SELECT THREAD_ID FROM performance_schema.threads WHERE PROCESSLIST_COMMAND = 'Sleep') ORDER BY SUM_ALLOCATED DESC LIMIT 5; - 对比两次
SHOW STATUS LIKE 'Threads_%'差值,若Threads_created明显高于Threads_connected,说明连接在频繁重建,大概率是客户端没复用好 - 开启慢日志 +
log_queries_not_using_indexes,确认是否有隐式全表扫描导致临时表暴增,这类问题会放大内存滞留效应
最麻烦的是混合场景:连接池配置合理,但某段代码里手动 conn = mysql.connect(...) 后忘了 close(),这种泄漏不会体现在池统计里,却会真实吃掉内存。得靠 pstack 抓 mysqld 进程堆栈,看线程卡在哪个 client fd 上。










