MySQL长连接本身不直接导致内存飙升,但配合不当的连接池配置、未释放的事务或低效查询,会让每个连接持续占用线程栈、排序缓冲区、临时表内存等资源,最终积少成多——1000个空闲连接就能吃掉1GB+内存。

MySQL长连接本身不直接导致内存飙升,但配合不当的连接池配置、未释放的事务或低效查询,会让每个连接持续占用线程栈、排序缓冲区、临时表内存等资源,最终积少成多——1000个空闲连接就能吃掉1GB+内存。
为什么SHOW PROCESSLIST里一堆Sleep连接却还在占内存
每个Sleep状态的连接仍保有独立的内存分配:线程栈(thread_stack,默认256KB–1MB)、sort_buffer_size、join_buffer_size、read_rnd_buffer_size等缓冲区。这些不是共享的,而是按连接独占分配。
- 连接数从200涨到800,仅
thread_stack就可能多占600MB+ -
wait_timeout默认是28800秒(8小时),意味着空闲连接能“合法”挂起整整一天 - 应用层用完
Connection没调close(),或连接池回收逻辑被绕过(比如异常分支遗漏),连接就卡在Sleep态不动 - 即使设置了
max_connections=1000,只要Threads_connected长期维持在950以上,就说明连接在堆积而非流转
HikariCP或Druid连接池常见配置陷阱
连接池不是开了就万事大吉,几个关键参数设错,等于给内存泄漏开绿灯。
-
connection-timeout(HikariCP)或maxWait(Druid)设得过大(如30秒),会导致获取连接失败前长时间阻塞,间接拖慢整体释放节奏 -
idle-timeout(HikariCP)默认是10分钟,但若业务偶发低峰,大量连接会“睡着”却不被回收;建议调为60–300秒 - 没开
leak-detection-threshold(HikariCP)或removeAbandonedOnBorrow(Druid),连接泄漏后完全无告警 -
minimum-idle设得过高(比如和maximum-pool-size一样),空闲期也维持大量连接常驻,白白占内存
如何定位哪个连接在偷偷吃内存
不能只看SHOW PROCESSLIST,要深入到内存分配层级。
- 查全局内存分布:
SELECT SUBSTRING_INDEX(event_name,'/',2) AS code_area, sys.format_bytes(SUM(current_alloc)) FROM sys.x$memory_global_by_current_bytes GROUP BY code_area ORDER BY SUM(current_alloc) DESC; - 找最耗内存的线程:
SELECT thread_id, event_name, SUM_NUMBER_OF_BYTES_ALLOC FROM performance_schema.memory_summary_by_thread_by_event_name ORDER BY SUM_NUMBER_OF_BYTES_ALLOC DESC LIMIT 5; - 确认该线程对应的应用连接:
SELECT * FROM performance_schema.threads WHERE THREAD_ID = ?;,再结合PROCESSLIST里的user和host反推服务实例 - 如果看到
memory/temptable/physical_ram或memory/sql/filesort排前三,基本就是某条GROUP BY或ORDER BY大查询在单个连接里反复申请内存
真正有效的释放策略不是“重启”,而是“分层切断”
光杀连接治标不治本,必须让释放动作可预期、可监控、可兜底。
- 数据库侧强制保活:
SET GLOBAL wait_timeout = 120;+SET GLOBAL interactive_timeout = 120;(注意:需应用层兼容短连接重连) - 连接池侧主动探测:
HikariCP加validation-timeout=3和connection-test-query=SELECT 1,避免拿到底层已断的连接 - 应用代码兜底:所有
try-with-resources或finally { conn.close() }必须覆盖全部分支,尤其catch里不能漏掉 - 上线前压测验证:
ab -n 5000 -c 200 'http://api/xxx'后立刻查SHOW STATUS LIKE 'Threads_connected';,确认峰值回落是否正常
连接数和内存占用不是静态指标,它们随查询复杂度、并发节奏、连接生命周期一起浮动。最容易被忽略的是:同一个连接执行完SELECT后,如果没提交事务,它依然会锁住缓冲区、阻止其他查询复用资源——哪怕它看起来只是Sleep着。











