mysql rss持续上涨通常不是真正内存泄漏,而是linux缓存虚高或innodb缓冲池预分配所致;需先用free -h看available、sync; echo 3 > /proc/sys/vm/drop_caches验证,再结合dmesg | grep -i oom、vmrss与innodb_buffer_pool_size配置比对判断。

确认是不是真泄漏,还是缓存虚高
看到 mysqld RSS 持续上涨,先别急着查 MySQL 内部——90% 的“内存泄漏”其实是 Linux 文件缓存或 MySQL 预分配行为。直接用 free -h 看 available 值,不是 free;再跑一次:sync; echo 3 > /proc/sys/vm/drop_caches。如果内存立刻掉一大截,说明只是内核页缓存,不用深挖。
真正要警觉的是:dmesg | grep -i oom 有输出,或 ps aux --sort=-%mem 显示 mysqld RSS 占比持续爬升、且 swap used 不为 0 并缓慢增长。
查 innodb_buffer_pool_size 是否设得过大
这是最常踩的坑:它不是“用多少占多少”,而是启动时就 mmap 一大块内存,长期驻留。设成 64G 机器的 50G,系统只剩 14G 给 OS 和其他进程,OOM Killer 很快就会动手。
-
mysql -e "show variables like 'innodb_buffer_pool_size';"—— 看配置值(单位 Byte) -
cat /proc/$(pidof mysqld)/status | grep VmRSS—— 看真实物理占用(KB) - 专用 DB 服务器:建议不超过总内存的 70%;混部环境(比如还跑 Redis/Nginx):别超 40%
- 动态调整可用:
SET GLOBAL innodb_buffer_pool_size = 32212254720;(30G),但注意innodb_buffer_pool_instances要能整除,否则实际生效值会被向下取整
抓线程级内存大户:谁在悄悄吃内存
每个连接都带一份 sort_buffer_size、read_buffer_size、tmp_table_size 副本。连接数从 50 到 500,光线程栈(thread_stack)就能多占近 1GB。
启用 Performance Schema 内存监控(若没开):
UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE 'memory/%';
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME IN ('global_instrumentation', 'thread_instrumentation');
然后执行:
SELECT THREAD_ID, PROCESSLIST_USER, PROCESSLIST_HOST,
sys.format_bytes(CURRENT_NUMBER_OF_BYTES_USED) AS used
FROM performance_schema.threads t
JOIN performance_schema.memory_summary_by_thread_by_event_name m
ON t.THREAD_ID = m.THREAD_ID
WHERE m.CURRENT_NUMBER_OF_BYTES_USED > 50*1024*1024
ORDER BY m.CURRENT_NUMBER_OF_BYTES_USED DESC
LIMIT 5;
重点关注:Threads_created 远大于 Threads_connected(比如每小时新增上千),说明应用没复用连接;SHOW PROCESSLIST 里大量 Sleep 状态且 Time 超过 300 秒,基本就是 wait_timeout 没调低。
关掉已废弃的 query_cache,禁用存储过程
query_cache_type=1 在 MySQL 8.0+ 已彻底移除,但如果你是从 5.7 升级上来的,配置文件里还留着,MySQL 启动时会默默加载旧逻辑,导致内存碎片化严重——表现为 memory/sql/Query_cache 在 performance_schema.memory_summary_global_by_event_name 里持续高位。
存储过程、函数、触发器在高并发场景下容易引发线程局部内存不释放,尤其当它们内部做循环或游标操作时。查一下:
SELECT db, type, COUNT(*)
FROM mysql.proc
WHERE db NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
GROUP BY db, type;
如果有结果,优先改造成应用层逻辑;实在要用,确保每次调用后显式 DEALLOCATE PREPARE,并监控 Com_dealloc_sql 是否跟得上 Com_prepare_sql。
真正难啃的点不在 SQL 或参数,而在 glibc malloc 的 arena 分配机制——多个线程竞争同一 arena 会导致内存无法归还给 OS,cat /proc/$(pidof mysqld)/smaps | awk '/^MMU:/ {sum+=$2} END {print sum}' 和 malloc_stats() 输出对比才能确认。这时候换 tcmalloc 或调大 MALLOC_ARENA_MAX 环境变量,比调 MySQL 参数更有效。











