wait_timeout无法解决长连接内存泄漏,因其仅清理真正空闲(Sleep)连接,而连接池心跳使其永不进入可清理状态;它不释放临时表、用户变量、预处理语句及线程私有内存,且不通知客户端,导致复用时报错;必须依赖连接池maxLifetime强制轮换连接,如HikariCP设maxLifetime=240秒(小于wait_timeout=300秒)并配合mysql_reset_connection()清理会话资源。

MySQL长连接不设生存周期,内存就一定会缓慢上涨——这不是概率问题,而是线程内存回收机制决定的。
为什么wait_timeout不能解决长连接内存泄漏
这个参数只对真正空闲(Sleep状态且无任何交互)的连接生效。但现代应用几乎都用连接池发心跳,比如每30秒执行一次SELECT 1,结果就是连接永远不进入“可被timeout清理”的状态。即使你把wait_timeout设成300秒,只要心跳间隔是299秒,它就形同虚设。
更关键的是:wait_timeout触发的是服务端单方面KILL,客户端不会收到通知,下次复用时直接报Lost connection to MySQL server during query或Communications link failure。
- 它不清理临时表、用户变量、预处理语句这些会话级资源
- 它不释放
sort_buffer_size、tmp_table_size等线程私有内存 - 它无法覆盖连接池主动保活的行为,纯属被动防御
必须用连接池的maxLifetime强制轮换连接
真正可控、可预测、能落地的方案,是让连接池自己管理连接的“最大存活时间”。MySQL本身不提供优雅续命机制,那就定期让它“退休重来”。
以HikariCP为例,关键配置不是idleTimeout(只管空闲),而是maxLifetime:
maxLifetime=1800000 # 30分钟,单位毫秒 connection-timeout=30000
这个值要满足两个条件:
- 必须小于MySQL侧的
wait_timeout(比如你设了300秒,那这里建议设为240秒或180秒) - 必须大于应用平均单次查询耗时 × 3,避免刚建好就销毁
- 如果用了
mysql_reset_connection()(MySQL 5.7.3+ / Connector/J 8.0.14+),可在归还连接前调用,清掉临时表和@变量,但依然不能替代maxLifetime
如何验证你的长连接是否在悄悄吃内存
别等OOM才查。日常盯住这三个指标,比看RSS更早发现问题:
-
SHOW PROCESSLIST里Sleep连接是否持续存在超过1小时,且数量稳定不降 -
SHOW GLOBAL STATUS LIKE 'Threads_created'是否在低流量时段仍缓慢上升(说明连接在不断重建) -
SELECT * FROM information_schema.INNODB_BUFFER_POOL_STATS中PAGES_DATA持续增长但PAGES_FREE不恢复
如果发现Sleep连接数长期 > 20 且每个存活 > 2 小时,基本可以判定连接池没启用maxLifetime,或者设得过大。
连接池配置与MySQL参数必须错开,不能完全对齐
很多人把maxLifetime和wait_timeout设成一样(比如都300秒),这是危险操作。一旦网络抖动、监控延迟或时钟漂移,可能引发雪崩式重连。
正确做法是制造“错峰”:
- MySQL侧
wait_timeout = 300(5分钟) - 连接池侧
maxLifetime = 240(4分钟),idleTimeout = 60(1分钟) - 这样90%的连接会在MySQL主动KILL前被池子自己回收,既避开通信中断,又防止资源滞留
最后提醒一句:mysql_reset_connection()虽好,但它不解决连接长期复用带来的线程栈累积问题——有些内存分配后就不会还给OS,只能靠换连接来彻底清空。这点最容易被忽略。


















