
MySQL运行一段时间后,mysqld进程占用数GB甚至更多内存,并不一定代表程序发生了内存泄漏。InnoDB会主动利用内存缓存数据页和索引,以减少磁盘读取;Linux也会把空闲内存用于文件缓存。因此,看到“已用内存很高”时,不应立即重启MySQL或清理系统缓存。
真正需要警惕的是:服务器可用内存持续下降、交换空间频繁读写、MySQL被OOM Killer终止、连接数异常增长,或者数据库响应时间随着内存压力明显上升。
本文介绍MySQL内存占用的主要来源、排查命令、常见配置风险和安全调整方法。示例主要面向Linux服务器上的MySQL 8.0与8.4,MariaDB及其他版本的变量和默认值可能不同,修改前应查看当前版本文档。
一、先判断是不是“真的内存不足”
先查看系统整体内存:
`bash free -h `
重点关注:
available:在不进行大量交换的情况下,系统仍可提供给程序使用的内存。buff/cache:Linux用于文件缓存的内存,需要时通常可以回收。Swap:交换空间总量与已使用量。
不要只看used。Linux将空闲内存用于缓存是正常行为,available持续偏低才更值得关注。
继续观察换页和运行状态:
`bash vmstat 1 10 `
如果si和so持续出现较高数值,表示系统正在频繁从交换空间读入或写出数据。数据库对延迟敏感,持续换页通常会明显影响查询性能。
查看内存占用最高的进程:
`bash ps -eo pid,user,%mem,rss,vsz,etime,comm,args –sort=-rss | head -n 20 `
其中:
RSS表示进程当前驻留在物理内存中的页面总量。VSZ表示虚拟地址空间大小,不能直接等同于真实物理内存消耗。%MEM表示进程驻留内存占系统物理内存的比例。
如果mysqld占用较高,还要确认服务器是否同时运行PHP、Java、Redis、Docker、监控面板或其他服务。数据库服务器与Web服务器混合部署时,不能把全部内存都分给MySQL。
二、MySQL为什么会占用大量内存
MySQL的内存大致可以分为全局内存、连接内存和临时工作内存。
1. InnoDB缓冲池
innodb_buffer_pool_size通常是MySQL最大的内存配置项,用于缓存InnoDB表数据、索引及相关页面。
查看当前配置:
`sql SHOW VARIABLES LIKE ‘innodb_buffer_pool_size’; `
查看更易读的GiB数值:
`sql SELECT @@innodb_buffer_pool_size / 1024 / 1024 / 1024 AS buffer_pool_gib; `
较大的缓冲池可以降低重复读取数据时的磁盘I/O,但配置过大可能挤压操作系统、连接缓冲区和同机运行的其他服务。
MySQL官方文档常将系统内存的50%至75%作为InnoDB缓冲池的参考范围,但这不是可以机械套用的固定比例。只有专用数据库服务器才可能采用较高比例;混合部署、小内存主机、容器或还运行备份任务的服务器应保留更多余量。
2. 每个连接可能分配的内存
MySQL除了全局缓冲区,还会为客户端连接、排序、连接操作、读取和线程栈分配内存。
常见相关变量包括:
`sql SHOW VARIABLES WHERE Variable_name IN ( ‘max_connections’, ‘sort_buffer_size’, ‘join_buffer_size’, ‘read_buffer_size’, ‘read_rnd_buffer_size’, ‘thread_stack’ ); `
这些缓冲区并非一定在每个连接建立时全部分配,但在查询需要时可能按连接或操作分配。如果同时把max_connections和多个会话级缓冲区设置得很大,峰值内存风险会迅速增加。
3. 临时表
复杂的排序、分组、去重和派生表可能使用内部临时表。相关限制可通过以下变量查看:
`sql SHOW VARIABLES WHERE Variable_name IN ( ‘tmp_table_size’, ‘max_heap_table_size’ ); `
不能简单地把这两个值调到数百MB或数GB。并发查询较多时,大型临时表会产生明显的内存压力;超过内存限制后转为磁盘临时表,又会增加I/O。
查看临时表统计:
`sql SHOW GLOBAL STATUS LIKE ‘Created_tmp%’; `
Created_tmp_disk_tables增长较快,只能说明有较多内部临时表落盘,还需要结合具体SQL、执行计划和业务负载判断,不能仅凭一个比率直接修改配置。
4. Performance Schema和其他缓存
Performance Schema、表定义缓存、线程缓存、二进制日志相关缓冲区及复制线程也会占用内存。单项可能不大,但在高连接数、大量表或复制环境中需要纳入总量评估。
三、检查MySQL当前连接是否异常
查看当前连接数量:
`sql SHOW GLOBAL STATUS LIKE ‘Threads_connected’; SHOW GLOBAL STATUS LIKE ‘Threads_running’; SHOW GLOBAL STATUS LIKE ‘Max_used_connections’; SHOW VARIABLES LIKE ‘max_connections’; `
这些指标分别反映:
Threads_connected:当前已建立连接数。Threads_running:当前正在执行而非休眠的线程数。Max_used_connections:服务启动以来同时连接数的峰值。max_connections:允许的最大客户端连接数。
如果Threads_connected很高,但大量连接长期处于Sleep状态,通常要检查应用连接池、连接释放逻辑和空闲超时,而不是先把max_connections继续调大。
查看会话:
`sql SHOW FULL PROCESSLIST; `
MySQL 8还可以查询Performance Schema:
`sql SELECT PROCESSLIST_ID, PROCESSLIST_USER, PROCESSLIST_HOST, PROCESSLIST_DB, PROCESSLIST_COMMAND, PROCESSLIST_TIME, PROCESSLIST_STATE, PROCESSLIST_INFO FROM performance_schema.threads WHERE TYPE = ‘FOREGROUND’ ORDER BY PROCESSLIST_TIME DESC; `
重点寻找:
- 长时间运行的查询。
- 大量休眠连接。
- 同一应用主机创建的异常连接。
- 大表排序、无索引连接和批量更新。
- 被锁等待阻塞的会话。
不要随意终止未知事务。结束连接可能导致事务回滚,回滚大型事务同样会消耗时间和资源。
四、从MySQL内部查看内存分配
MySQL 8可以通过Performance Schema和sys库查看部分内部内存分配。
先查询按分配类型汇总的当前内存:
`sql SELECT event_name, current_alloc, high_alloc FROM sys.memory_global_by_current_bytes ORDER BY current_alloc DESC LIMIT 20; `
按代码区域汇总:
`sql SELECT SUBSTRING_INDEX(event_name, ‘/’, 2) AS code_area, sys.format_bytes(SUM(current_alloc)) AS current_alloc FROM sys.x$memory_global_by_current_bytes GROUP BY SUBSTRING_INDEX(event_name, ‘/’, 2) ORDER BY SUM(current_alloc) DESC; `
如果查询结果为空或不完整,可能是相关Performance Schema内存监控未启用。查看状态:
`sql SELECT NAME, ENABLED FROM performance_schema.setup_instruments WHERE NAME LIKE ‘memory/%’ LIMIT 20; `
启用更多监控会带来一定开销,生产环境不应在不了解影响时批量修改全部监控项。MySQL内部统计与操作系统看到的RSS也可能不完全一致,因为内存分配器、映射文件和已释放但尚未归还操作系统的内存都会影响结果。
五、检查是否发生OOM或MySQL异常重启
查看内核日志:
`bash sudo journalctl -k –since “24 hours ago” | grep -Ei ‘out of memory|oom|killed process’ `
也可以执行:
`bash dmesg -T | grep -Ei ‘out of memory|oom|killed process’ `
查看MySQL服务日志:
`bash sudo journalctl -u mysql –since “24 hours ago” `
部分发行版的服务名可能是mysqld:
`bash sudo journalctl -u mysqld –since “24 hours ago” `
如果MySQL被OOM Killer终止,说明系统在某个时刻无法满足内存需求。此时应结合故障前后的连接数、查询任务、备份、容器限制和其他进程占用判断,而不能只把缓冲池减小后就结束排查。
六、如何安全调整InnoDB缓冲池
先记录当前值和服务器可用内存:
`sql SHOW VARIABLES LIKE ‘innodb_buffer_pool_size’; `
MySQL 8支持动态调整缓冲池,例如设置为2GiB:
`sql SET GLOBAL innodb_buffer_pool_size = 2147483648; `
调整结果可能根据缓冲池实例和块大小自动取整。查看调整状态:
`sql SHOW STATUS LIKE ‘Innodb_buffer_pool_resize_status’; `
确认运行稳定后,再将配置写入MySQL配置文件,避免重启后恢复旧值。常见写法如下:
`ini [mysqld] innodb_buffer_pool_size=2G `
配置文件位置因发行版和安装方式而异,常见目录包括/etc/mysql/和/etc/my.cnf.d/。修改前应先确认实际加载顺序:
`bash mysqld –verbose –help 2>/dev/null | grep -A 1 “Default options” `
不要一次大幅缩小生产数据库的缓冲池。缓冲池减小后,缓存命中率可能下降并增加磁盘I/O,应在低峰期分阶段调整,同时监控查询延迟、磁盘延迟和数据库吞吐量。
七、连接数和会话缓冲区怎么优化
不要盲目提高max_connections
如果应用正常峰值只需要100个连接,把max_connections设置为数千并不能提升性能,反而扩大错误配置或连接泄漏时的内存风险。
合理做法是:
- 统计正常与高峰连接数。
- 检查应用连接池上限。
- 为突发流量保留适当余量。
- 设置连接获取超时,避免请求无限堆积。
- 修复未释放连接和过长事务。
谨慎设置会话缓冲区
sort_buffer_size和join_buffer_size等变量可能在查询执行期间按会话分配。把它们设置得很大,可能让少量复杂查询在高并发时消耗大量内存。
优化顺序应当是:
- 先找出慢SQL和无索引查询。
- 使用
EXPLAIN或EXPLAIN ANALYZE检查执行计划。 - 为连接、排序和筛选条件设计合适索引。
- 限制报表、导出和批处理任务的并发。
- 确认仍有需要后,再小幅调整缓冲区。
参数扩大不能替代SQL和索引优化。
八、内存突然升高时如何应急处理
1. 先保存现场
记录:
`bash date free -h vmstat 1 5 ps -eo pid,%mem,rss,vsz,etime,comm,args –sort=-rss | head -n 20 `
同时保存MySQL连接、运行查询和关键状态指标。
2. 停止非关键高消耗任务
如果确认内存峰值来自报表、全库扫描、批量导入或备份任务,可以先停止任务来源或降低并发。终止SQL前应判断事务回滚成本。
结束指定MySQL连接:
`sql KILL CONNECTION 12345; `
只终止正在执行的语句并保留连接:
`sql KILL QUERY 12345; `
将12345替换为实际连接ID。
3. 限制流量入口
如果连接暴涨来自网站突发流量或异常请求,应在应用、反向代理、连接池或防火墙层限流,而不是让所有请求直接压到数据库。
4. 最后才考虑重启
重启MySQL会中断连接,未完成事务需要恢复,缓冲池重新预热期间性能可能下降。只有在服务已经无法正常响应、内存仍失控且其他措施无法恢复时,才应在确认备份、复制和业务影响后进行重启。
不要使用以下命令作为常规优化手段:
`bash sync echo 3 | sudo tee /proc/sys/vm/drop_caches `
清理Linux文件缓存通常不能解决MySQL配置或查询问题,还可能让后续读取重新访问磁盘,造成性能下降。
九、容器中的MySQL要额外检查什么
MySQL运行在Docker或Kubernetes中时,需要同时查看宿主机内存与容器限制。
Docker可执行:
`bash docker stats docker inspect CONTAINER_NAME –format ‘{{.HostConfig.Memory}}’ `
如果容器内存限制小于MySQL配置可能达到的峰值,即使宿主机还有空闲内存,容器也可能被终止。
需要综合估算:
- InnoDB缓冲池。
- MySQL基础内存和Performance Schema。
- 并发连接与会话缓冲区。
- 临时表和复杂查询。
- 复制、备份与维护任务。
- 容器运行时开销和安全余量。
不要把innodb_buffer_pool_size设置成接近容器内存上限。
十、推荐的完整排查顺序
遇到MySQL内存占用过高时,可以按照下面的顺序处理:
- 使用
free -h确认available内存与Swap状态。 - 使用
vmstat确认是否持续换页。 - 使用
ps判断MySQL是否确实是主要内存来源。 - 检查
innodb_buffer_pool_size及同机其他服务。 - 对比当前连接数、运行线程和历史连接峰值。
- 使用
SHOW FULL PROCESSLIST定位长查询与大量休眠连接。 - 查询
sys.memory_global_by_current_bytes分析内部内存分配。 - 检查OOM日志、MySQL错误日志和异常重启记录。
- 优先修复连接泄漏、慢SQL、无索引查询和失控任务。
- 分阶段调整缓冲池、连接上限或会话缓冲区。
- 持续观察查询延迟、磁盘I/O、Swap和内存曲线。
- 资源确实不足时,再考虑扩容或拆分数据库。
十一、怎样预防内存问题再次发生
建议持续监控:
- 系统可用内存和Swap读写。
- MySQL进程RSS。
- InnoDB缓冲池使用情况。
- 当前连接数、运行线程和连接峰值。
- 慢查询数量与执行时间。
- 临时表及磁盘临时表增长。
- OOM事件和MySQL重启次数。
- 数据库响应时间、磁盘延迟和错误率。
告警不应只设置成“MySQL内存超过80%”。对于专用数据库服务器,高内存占用可能是正常状态;更有效的告警应结合可用内存、换页、OOM、连接增长和业务延迟。
总结
MySQL占用大量内存并不一定是故障。InnoDB缓冲池利用内存缓存数据,通常能够提升查询性能。判断问题时,应重点观察系统可用内存、Swap、OOM记录、连接数、运行查询和业务延迟,而不是只看mysqld进程的一个百分比。
常见风险包括缓冲池设置过大、max_connections不合理、会话缓冲区过高、连接泄漏、复杂查询并发执行,以及容器限制小于数据库实际需求。
正确处理方式是先保存现场并定位内存来源,再优化SQL、连接池和任务并发,最后根据监控结果调整参数或扩容。直接清理Linux缓存、强制结束MySQL或频繁重启,通常只能暂时降低数字,不能解决真正的内存问题。




