news 2026/9/26 12:28:20

MySQL内存占用高?从缓冲池到连接线程的排查与优化实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL内存占用高?从缓冲池到连接线程的排查与优化实战

1. 故障现象:MySQL 内存占用到底该怎么看

先说个真实场景。去年我接手一台线上服务器,配置是 16G 内存,跑着 MySQL 8.0 和几个 Java 应用。某天监控报警,说内存使用率飙到 95% 以上。我登录服务器一看,free -h显示 used 接近 15G,但细细一算,Java 应用加起来也就占了 6G 左右,剩下的内存几乎全被 mysqld 吃掉了。

很多朋友一遇到 MySQL 内存占用高,第一反应是“是不是内存泄漏了”,然后急着重启数据库。其实这个结论下得太早。MySQL 内存占用高,有相当一部分是正常现象,特别是 InnoDB 引擎的缓冲池和操作系统的 Page Cache 机制,会导致内存看着很高,但实际并不影响性能。真正需要警惕的是那部分“只涨不降、涨到系统 OOM”的内存。

排查之前,先把口径统一了。Linux 下看内存,别只看free -m里的 used 列,要看这几项:

指标含义说明
total物理内存总量服务器实际内存
used已使用内存包含 cache/buffer
free完全空闲的内存不含任何缓存
available可分配给新进程的内存包含可回收的 cache
buff/cache文件缓存可回收,不用太紧张

重点记住一句话:free命令里的 used 包含了 buff/cache,这部分内存是 Linux 的文件页缓存,MySQL 读了磁盘数据后,操作系统会把这些数据留在内存里,下次读取直接命中缓存,速度飞快。如果 MySQL 内存高主要高在 buff/cache 上,这其实是好事,根本不叫问题。真正需要排查的是RES(常驻内存)这一列,也就是 mysqld 进程真正占用的物理内存。

所以第一步不是急着优化,而是先精确定位:到底是 mysqld 的哪个内存区域吃掉了内存,是 InnoDB Buffer Pool,还是连接线程,还是排序缓冲,还是其他临时结构。定位错了方向,后面所有的优化都白做。

2. 先分清:正常占用 vs 异常占用

2.1 InnoDB Buffer Pool 是内存大户,但这不算故障

InnoDB Buffer Pool(以下简称 BP)是 MySQL 内存占比最大的部分,默认配置下可能占物理内存的 60% 到 80%。它缓存的是表数据页和索引页,相当于把磁盘上的热点数据预加载到内存里,减少磁盘 IO。

举个生活化的例子,BP 就像你家楼下的便利店,老板会把卖得最好的几款饮料放在冰柜最显眼的位置。你每次来买,不用让店员去仓库翻,直接拿起就走。如果冰柜太小,每次都要去仓库取,那顾客体验就很差。MySQL 的 BP 就是这个冰柜,大小决定了一半以上的性能。

既然 BP 是“缓存”,它占内存就是合理的。但问题在于:如果innodb_buffer_pool_size配得太大,超过了物理内存减去系统和其他应用所需的总和,它就会跟别的进程抢内存,严重时触发 OOM Killer,直接把 mysqld 杀掉。我在云服务器上见过不少事故:用户把 4G 服务器上的 MySQL 配置直接套用了 32G 服务器的配置文件,innodb_buffer_pool_size=24G,结果系统一启动就卡死,日志里全是 out of memory。

2.2 连接线程和排序缓冲:临时分配却常被忽略

如果说 BP 是稳定占用,那连接线程和各类临时缓冲就是“喝水”型占用:来一个连接就分配一部分内存,连接一多,内存水涨船高。

MySQL 每个连接在服务端至少要分配两个缓冲区:sort_buffer_size和join_buffer_size。注意,这两个参数是“每连接、每操作”都会分配的,不是全局共享。假设你的sort_buffer_size=4M,同时有 200 个连接在跑 ORDER BY 排序,那光排序缓冲就可能吃掉 800M。同理,join_buffer_size在BKA、BNL等连接算法中也按连接分配。再加上read_buffer_size、read_rnd_buffer_size、net_buffer_length,一个活跃连接的内存开销随随便便能到十几兆甚至几十兆。

更隐蔽的是performance_schema。这个库默认是开启的,它会在内存里维护大量统计表,比如每个线程的事件记录、每个语句的摘要、锁等待信息等。我排查过一个小型实例,业务量并不大,但performance_schema居然吃了 2G 多内存。这个参数不像 BP 那样显眼,但它真的会在后台悄悄积累。

还有临时表。MySQL 8.0 默认临时表存储引擎是 TempTable,内存中的临时表达到一定阈值后才转磁盘。如果业务里有大批量 GROUP BY、DISTINCT、子查询,临时表内存占用会瞬间暴涨。

2.3 关键认知:MySQL 不释放内存是常态

这里必须强调一个很多新手想不通的问题:为什么我删掉了大量数据,内存却不降?

原因有两个层面。

第一,InnoDB Buffer Pool 本身是“缓存”,它里面存的数据页即使被删除和修改,只要内存足够的空间,MySQL 不会主动淘汰这些页。只有当需要加载新数据页且内存池已满时,才会按照 LRU 算法换出一些旧页。所以删数据不会导致 BP 内存下降,这是设计使然。

第二,glibc 的malloc和free机制决定了,进程释放的内存未必交还给操作系统,而是留在进程的堆内存空闲链表中,供后续的分配复用。这是个优化策略:假设 MySQL 某个瞬间并发很高,分配了 2G 内存做排序,排序完成后,这 2G 内存并不会立刻归还给系统。如果下一次查询又需要排序,直接复用这 2G 空闲内存,比重新向内核申请要快得多。这个机制叫arena缓存。所以在top里看着 mysqld 的 RES 很高,不代表它一直在用这些内存,只能说“它曾经用过这些内存”。

理解了这一点,你就明白为什么网上很多教程教你“重启 MySQL 释放内存”是治标不治本。重启后内存确实降下来了,但重新跑热数据后内存又会涨回来,而且重启期间业务中断,代价远大于那点内存收益。除非内存真的已经成为瓶颈且无法通过参数调整解决,否则不要用重启来“释放内存”。

3. 系统层面排查:定位内存到底被谁吃掉

3.1 用 top 和 pidstat 确认 mysqld 的实际内存

排查第一步,用top看进程排行:

top -o %MEM

按内存降序排列,找到 mysqld 那一行,记下 PID,然后用如下命令观察 mysqld 的实时内存变化:

pidstat -r -p PID 1 10

pidstat会输出minflt/s(每秒缺页数)、majflt/s(每秒主缺页数)、VSZ和RSS。如果你发现majflt/s频繁出现非零值,说明 MySQL 在不断访问磁盘上的数据页,内存可能真的不够用了;如果RSS一直稳定高位且majflt/s为零,说明数据都在内存里,性能是好的,只是数字看着吓人。

再看一下整机的内存压力:

cat /proc/meminfo

重点关注CommitLimit和Committed_AS。Committed_AS是内核承诺给所有进程的内存总和,如果它超过CommitLimit,系统启用 overcommit 后可能触发 OOM。

3.2 查看 MySQL 内部内存统计视图

MySQL 5.7 及以上版本提供了内存统计表,可以直接查询各内存组件的使用量:

SELECT * FROM sys.memory_by_thread_by_current_bytes;

这个视图按线程展示当前内存占用,能看到哪些连接最“吃”内存。如果某个连接的内存占用异常高,定位到PROCESSLIST_ID后,可以结合SHOW PROCESSLIST看它在执行什么 SQL。

再查全局的内存消耗状况:

SELECT EVENT_NAME, CURRENT_NUMBER_OF_BYTES_USED FROM performance_schema.memory_summary_global_by_event_name ORDER BY CURRENT_NUMBER_OF_BYTES_USED DESC LIMIT 20;

这个查询会列出所有内存事件(memory event)的当前使用量。我一般按字节数排序,重点看前二十项。里面常见的几类如下所示。

EVENT_NAME 关键字对应内存区域
memory/innodb/buf_buf_poolInnoDB 缓冲池
memory/sql/Query_cache查询缓存(8.0 已移除)
memory/sql/thd::main_mem_root连接线程基础内存
memory/sql/THD::mem_root语句执行内存
memory/sql/derived_table派生表
memory/performance_schema性能字典自身内存

如果performance_schema相关的 EVENT_NAME 占到了总内存很大比例,可以考虑关闭它或缩小它的采集项配置。生产环境中很多性能监控工具(如 PMM、Prometheus mysqld_exporter)依赖它提供指标,不能全关,但可以调整performance_schema_*_size相关参数来控制采集表的大小。

4. 参数优化:抓住核心配置逐项调整

4.1 InnoDB Buffer Pool:设置合理的上限

innodb_buffer_pool_size是 MySQL 内存设置里最重要也最敏感的一个参数。设置公式没有绝对标准,但有一条安全底线:总内存的 50% 到 70%,同时必须留出给操作系统和 MySQL 其他结构(连接线程、排序缓存、临时表、binlog cache 等)的余量。

如果服务器上只有 MySQL 一个数据库服务,没有部署其他重量级应用,可以参考:

总内存 8G: innodb_buffer_pool_size = 4G ~ 5G 总内存 16G: innodb_buffer_pool_size = 8G ~ 10G 总内存 32G: innodb_buffer_pool_size = 16G ~ 20G 总内存 64G: innodb_buffer_pool_size = 32G ~ 40G

注意,MySQL 8.0.30 及以后可以设置innodb_buffer_pool_size为动态值,不需要重启。但是调整后要观察一段时间,不能一次性从 4G 调到 20G,容易在初始化缓冲池时造成性能抖动。

还有一个细节:innodb_buffer_pool_instances。如果 BP 设置得很大(比如超过 16G),建议把实例数调大,比如设成 8 或 16。每个 Buffer Pool Instance 有独立的 free list 和 mutex,能减少并发访问时的锁竞争。这个参数在 MySQL 8.0.28 之后可以在线调整,但注意它只能整倍数变化。

4.2 连接数与各类 buffer:防止“蚂蚁搬家”式占用

max_connections这个参数是内存占用的乘法器。每增加一个连接,就要预留一组线程栈、net buffer 和可能用到的排序/连接缓冲。如果max_connections设成 1000,但实际业务只有 200 并发,那剩下的 800 个连接虽然没建立,但 MySQL 需要按最大值预留线程缓存,内存开销照样存在。

建议按实际业务并发设置,一般是 200 到 500,不要盲目设成 32767。配合max_used_connections指标观察,线上运行一段时间后,看SHOW GLOBAL STATUS LIKE 'Max_used_connections'的值,max_connections设置为这个峰值的 1.5 到 2 倍即可。

排序和连接缓冲的调整策略更简单:能小则小,需要用时再临时调大单个会话的值。

sort_buffer_size = 2M join_buffer_size = 2M read_buffer_size = 1M read_rnd_buffer_size = 1M

很多教程把sort_buffer_size设成 64M,这是误人子弟。排序缓冲按连接分配,200 个连接每个 64M,瞬间 12.8G 没了。通常 2M 到 4M 完全够小查询用,大排序可以通过增大磁盘临时表的容量或者优化 SQL(比如加索引避免 filesort)来解决,而不是靠堆内存。

如果你有特定的大排序需求,可以在会话级别临时设大:

SET SESSION sort_buffer_size = 64 * 1024 * 1024;

这样只影响当前连接,不会波及全局。

4.3 临时表内存阈值:避免 Group By 拖垮内存

如果业务查询中有大量GROUP BY、DISTINCT或派生表,临时表会频繁从内存表转成磁盘表。这里有一个关键参数tmp_table_size和max_heap_table_size,两者取较小值作为内存临时表的大小上限。

默认配置下这两个参数可能是 16M 到 32M,对于复杂报表查询确实不够。但是同样要注意“每连接分配”的特性。我通常把这两个参数设置在 64M 到 128M 之间,然后监控Created_tmp_disk_tables和Created_tmp_tables状态值。如果磁盘临时表占比过高,说明内存临时表太小,SQL 需要中转磁盘;如果内存临时表占用本身异常高,说明 SQL 写得有问题,需要优化而不是加内存。

判断方法:

SHOW GLOBAL STATUS LIKE 'Created_tmp%';

当Created_tmp_disk_tables / Created_tmp_tables的比例大于 25% 时,才值得考虑调大内存临时表参数;否则保持默认即可。

4.4 performance_schema:低调的内存吞噬者

MySQL 5.7 之后performance_schema默认开启,意在采集各类性能事件。但它内部每个采集项都有内存开销。举个例子,events_statements_summary_by_digest会缓存所有 SQL 语句的指纹摘要,默认情况下最多缓存 1000 种语句模式,如果业务里动态 SQL 非常多,这个表会持续增长。

处理方式有两个方向:如果线上不需要细粒度性能诊断,可以关闭:

performance_schema = OFF

但副作用是很多监控工具(比如 mysqld_exporter 的部分指标)拿不到数据。所以我更推荐第二个方式:按需裁剪采集项。

performance_schema_max_table_instances = 1000 performance_schema_max_thread_instances = 500 performance_schema_max_statement_classes = 200

具体裁剪哪些项,要结合查询结果来看:

SELECT EVENT_NAME, CURRENT_NUMBER_OF_BYTES_USED FROM performance_schema.memory_summary_global_by_event_name WHERE EVENT_NAME LIKE '%performance_schema%' ORDER BY CURRENT_NUMBER_OF_BYTES_USED DESC;

把占用最大的几个采集项单独设小。这需要一点耐心,但效果明显。

4.5 其他容易被忽略的内存选项

下面这几个参数虽然不显眼,但在特定业务场景下能影响几百兆甚至上G的内存。

  • table_open_cache:打开表缓存,每个缓存条目都有元数据和分区信息。如果表非常多(几千张),这个值设成 2000 的默认值可能不够用,但设成 10000 又会增加内存。结合Open_Tables和Opened_tables状态值来判断。

  • innodb_log_buffer_size:默认 16M 通常没问题。如果日志写入非常频繁(大量更新、删除),可以调到 64M,减少日志刷盘次数。这个参数对内存影响不大,但容易被忽略。

  • innodb_adaptive_hash_index:自适应哈希索引,默认开启。它在高并发点查场景能提升性能,但也会消耗内存。如果表上的等值查询不多,或者内存非常紧张,可以考虑关闭。注意,这个参数在 8.0 中仍然是动态的,修改后观察响应性能再确定是否长期生效。

  • max_heap_table_size和tmp_table_size:前面提过,放一起说,取小值生效。

再补充一个binlog_cache_size,它只在事务提交时分配,默认 32K。如果业务中有大批事务,这个值过小会导致磁盘临时写入频繁,过大则每个连接都要预留空间。一般不要超过 1M,除非你确认有大事务场景。

5. 实操记录:一次 16G 内存服务器的完整排查过程

5.1 现场信息收集

为了让你少走弯路,我用案例方式完整走一遍流程。假设这是一台 16G 内存的云服务器,运行 MySQL 8.0.34,部署了 4 个 Java 微服务(合计占用约 4G 内存)。报警记录显示内存使用率 93%,但服务并未宕机,只是频繁触发 swap。

登录服务器后的操作顺序如下。

第一步,确认整机内存分配:

free -h

输出结果为:

total used free shared buff/cache available Mem: 15Gi 8.1Gi 1.2Gi 181Mi 5.7Gi 7.0Gi

used 8.1G,available 7G,表面上看还有余量,但能看到已经出现 swap 使用了。再看 swap:

swapon --show

发现 swap 分区已经用掉 512M,说明内存压力确实存在,不止是“看着高”这么简单。

第二步,定位 mysqld 的实际内存占用:

top -p $(pgrep mysqld) -b -n 1

mysqld 的 RES 显示 6.8G。然后查看 MySQL 内部的详细内存统计:

SELECT EVENT_NAME, CURRENT_NUMBER_OF_BYTES_USED/1024/1024 AS MB FROM performance_schema.memory_summary_global_by_event_name ORDER BY CURRENT_NUMBER_OF_BYTES_USED DESC LIMIT 15;

结果中最显眼的几项:

  • memory/innodb/buf_buf_pool:5.0G
  • memory/sql/THD::mem_root:380M
  • memory/sql/derived_table:226M
  • memory/performance_schema:800M
  • memory/innodb/adaptive_hash_index:365M

从这个结果能看到两个异常点:连接线程相关内存 380M 偏高,说明当前活跃连接数量不少,且涉及复杂查询;derived_table 占 226M,说明有大量派生表在内存中处理;performance_schema 占 800M,对于一个 16G 服务器来说偏大。

第三步,查看当前连接数和状态:

SHOW GLOBAL STATUS LIKE 'Threads_connected'; SHOW GLOBAL STATUS LIKE 'Max_used_connections';

Threads_connected当前 280,Max_used_connections340。再查看SHOW PROCESSLIST,发现其中有大量来自同一个应用服务的连接,正在执行带ORDER BY的列表查询。

第四步,确认这些连接的内存分配参数:

SHOW VARIABLES LIKE 'sort_buffer_size'; SHOW VARIABLES LIKE 'join_buffer_size'; SHOW VARIABLES LIKE 'max_connections';

排序缓冲 4M、连接缓冲 4M、最大连接数 1000。按最大连接数 1000 算,光排序和连接缓冲就可能分配 8G,加上基础线程内存,符合现状。

5.2 优化动作与效果

基于现场数据,我做了以下调整:

  1. innodb_buffer_pool_size从 6G 调到 5G。为什么调小?不是因为它本身不正常,而是因为这台服务器还要跑 Java 应用,又开启了 swap。留出更多内存给系统级缓存和 Java 堆,避免 OOM。

  2. max_connections从 1000 调到 500。根据Max_used_connections只有 340,500 完全够用,但在内存层面砍掉了近一半的预留线程空间。

  3. sort_buffer_size和join_buffer_size都从 4M 降到 2M。新版 MySQL 是动态分配机制,小查询用不了那么多,大查询在会话级临时调大。

  4. performance_schema保持开启,但把一些非必要的采集表关掉:

UPDATE performance_schema.setup_instruments SET ENABLED = 'NO' WHERE NAME LIKE 'wait/%';

注意这个操作要谨慎,会影响后续诊断数据的收集。我更推荐在配置文件里加入:

performance_schema_max_table_instances = 500 performance_schema_max_thread_instances = 300 performance_schema_max_statement_classes = 150
  1. tmp_table_size从 32M 调到 64M,同时把max_heap_table_size也调到 64M。

调整完后重启 MySQL(参数里有需要重启生效的项),观察两周。

第一天的内存情况:

mysqld RES: 5.1G free available: 9.4G 使用率: 42%

七天后的稳定状态:

mysqld RES: 5.4G free available: 8.2G 使用率: 48% swap 使用 0K

整体内存压力大幅缓解,且从监控看查询响应时间波动不大。我特别注意到,由于 sort_buffer 调小,部分复杂排序 SQL 的执行时间从 80ms 涨到 120ms,可接受,但如果业务更敏感,我会用创建合适索引来消除 filesort,而不是开大 buffer。

5.3 经过较长时间运行,操作系统关闭了大页内存

这里有个后验细节。服务器在调整后跑了大概一个月,某天突然发现free -h里 buff/cache 涨到了 7G 多,而 mysqld 的 RES 只有 3.8G。一开始有点疑惑,以为是 MySQL 数据访问变少了。后来查了 IO 状态,发现是业务在做一个大范围的表扫描操作,操作系统把扫描到的数据页都缓存下来了,属于正常现象,不需要处理。数据扫描结束,buff/cache 会逐步回落。

所以说,内存优化不是一次性工作,需要持续监控并理解各类内存的真实含义。看到内存高先别慌,区分“缓存型占用”和“消耗型占用”,前者是朋友,后者才是敌人。

6. 常见问题与排查技巧实录

6.1 问题一:为什么执行大查询后内存涨了不降

这是最高频的问题。前面讲过 glibc 的 arena 缓存机制,这里再展开一下。

当 MySQL 执行一个需要 500M 排序缓冲的查询时,malloc会向内核申请 500M 内存。查询执行完,调用了free,这 500M 内存被释放,但 glibc 并没有马上用munmap把内存段归还内核,而是放在进程的空闲链表中。这样下次排序时,MySQL 可以直接从空闲链表取内存,省去了系统调用开销。

从外部看,mysqld 的 RES 一直保持在高位,即使空闲链接都释放了也不会降。这不能通过“调参数”来解决,因为这是运行库层面为了性能而做的取舍。应对办法有两个方向:

第一种,如果内存非常紧张,想强制让进程归还内存,可以用malloc_trim相关工具,但 MySQL 官方并未内置,需要挂载到进程里操作,风险很高。生产环境我不建议这样做。

第二种,从源头限制单条大查询的内存使用。在 MySQL 中没有像max_memory_size这样直接限制所有内存去向的单一变量,但可以限制单条 SQL 的执行时间(max_execution_time),或者对特定用户设置资源组限制。MySQL 8.0 的资源组功能可以给线程组设定 CPU 和内存限制,但内存限制只有 virtual memory 维度,实际效果有限。

所以我的结论是:大查询导致的内存残留,能不处理就不处理。只要还有 available 内存,硬件层面撑得住,就别折腾。真到了 OOM 边缘,优化 SQL 才是治本。

6.2 问题二:mysqld 被 OOM Killer 杀死,常见模式

一种常见模式是:MySQL 的innodb_buffer_pool_size设成了物理内存的 80%,再加上连接线程、临时表、排序缓冲的消耗,在业务高峰一下越过内存红线,触发内核 OOM Killer。

因为 OOM Killer 的分值(oom_score)是根据进程内存占用大小来定的,mysqld 作为最大的内存占用者,经常被优先选中。

查看内核日志确认:

dmesg | grep -i oom journalctl -k | grep -i oom

如果找到类似 “Out of memory: Kill process 12345 (mysqld), score 750” 的记录,说明确实是 mysqld 被杀了。

对策:修改/proc/PID/oom_score_adj保护 mysqld 进程。写进 systemd service 文件的ExecStartPre或/etc/sysctl.conf都行,但最保险的方式是在 MySQL 的 systemd unit 里加:

OOMScoreAdjust=-800

把 mysqld 的 OOM 分数调低,让它不容易被选中。但这不是万能药:如果系统整体内存真的耗尽,内核还是会根据各种因素挑一个进程杀掉,所以 OOMScoreAdjust 只起到降低概率的作用。根子还是要把内存占用控制住。

另外,如果使用非 systemd 方式管理 MySQL,可以在启动脚本里加上:

echo -800 > /proc/$(pgrep mysqld)/oom_score_adj

这种“保护”仅限手动启动场景,且每次启动后都要执行。

6.3 问题三:MySQL 内存缓慢增长,怀疑是内存泄漏

MySQL 本身的稳定性已经很高,真正因为代码缺陷导致的内存泄漏相对少见。更多情况是以下原因:

第一,performance_schema的某些表如果开启了sum字段更新,会持续为语句、等待事件分配内存,导致内存曲线缓慢上升。针对这个场景,可以定期清理:

TRUNCATE TABLE performance_schema.events_statements_history_long;

第二,Prepared Statement 没有正确释放。客户端每执行一次PREPARE就会在服务端保存一份解析后的 SQL 和参数类型,如果连接不断开,这些资源不会释放,只会在超过max_prepared_stmt_count或连接关闭时才清理。检查应用层是否有PreparedStatement未关闭的逻辑,是 MySQL 内存慢涨排查中的高性价比动作。

第三,临时内存表碎片化。内存表(MEMORY 引擎或者 TempTable 引擎)在频繁增删后会产生碎片,导致内存总量只涨不降。如果是 MEMORY 引擎的业务表,考虑改造成 InnoDB 并优化索引。

如果确认内存确实持续增长而业务没有对应增长,可以用性能字典的memory_summary_global_by_event_name多次采样对比,找出哪类 EVENT_NAME 在持续增长,然后按图索骥去查对应的连接和 SQL。

6.4 排查技巧速查表

症状首选命令/查询紧急处置
内存占用高但可用内存充足free -h看 available不需要处置
mysqld RES 持续上涨多次查询 memory_summary_global_by_event_name 对比按 EVENT_NAME 定位组件
频繁 swapvmstat 1 10看 si/so调小 BP,降 max_connections
出现 OOM`dmesggrep oom`
连接数多但并发不高SHOW STATUS LIKE 'Threads_connected'调低 max_connections,检查连接池配置
排序后内存不释放topRES 高位但 stable无需处理,观察 available
performance_schema 内存大查询 memory_summary_by_event_name裁剪采集项或关闭

7. 调优之外的长期策略

参数调优能在短期内缓解内存压力,但真正想要根治,需要从架构和习惯两个层面下手。

第一层,监控体系建设。给 MySQL 配上 mysqld_exporter + Prometheus + Grafana 是基础方案,重点盯住四个指标:process_resident_memory_bytes、process_virtual_memory_bytes、mysql_global_status_threads_connected、mysql_global_status_innodb_buffer_pool_pages_free。有了趋势数据,你能在内存告警之前看到“连接数在爬升”“空闲页在减少”,而不是等问题爆发了才去现场救火。

第二层,SQL 治理。大部分内存问题最终都能追溯到某几条 SQL。可以定期查看慢查询日志,重点关注ORDER BY、GROUP BY、多表JOIN且没有走索引的语句。一条 10 万行数据的全表排序可能就吃掉 100M 内存,如果这类语句一分钟跑一次,内存压力自然大。SQL 优化能从根本上减少临时表和排序缓冲的申请次数。

第三层,容量评估与基线管理。每台服务器上的 MySQL 内存占用并不是固定值,它和业务流量密切相关。建议每周或者每次大版本发布后,记录一组基线数据(连接数、QPS、内存占用率),建立正相关性分析。比如“当 TPS 达到 2000 时,内存占用约 5.2G”,有了这种经验数据,下一次容量规划就有据可依。

第四层,定期清理连接泄漏。查代码里的数据库连接管理,看看连接是否在 finally 块中关闭,连接池的空闲连接超时时间是否合理。曾经遇到过一个应用把连接池最大连接数设到 500 而实际只需要 50,导致 MySQL 端始终保持大量空闲连接,每个连接 3M 左右,白白浪费 1G 多内存。

8. 一些心里话

排查 MySQL 内存问题,最考验人的不是会不会敲命令,而是遇到“看着很吓人的高内存”时还能不能冷静分析。我刚入行的时候,看到free显示 used 90% 就想重启 MySQL,后来被前辈拦下“你看看 buff/cache”。现在回头看,那些年踩过的坑都成了判断经验。

如果你现在正被这个问题困扰,我的建议是:先花十分钟做诊断,再花五分钟想清楚业务是否真的受影响。如果没有性能下降,没有 swap,没有 OOM,大可不必纠结内存数字。如果有,从 Buffer Pool 这个最大头开始调,然后是连接数,再是各类 buffer,最后考虑 SQL 层面。这个顺序记下来,你基本上能解决 70% 的 MySQL 内存焦虑。剩下的 30%,就要靠对业务特征的理解和长期监控数据的沉淀了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 12:28:04

移动硬盘不显示原因与零风险修复指南

1. 为什么移动硬盘插上电脑却像“隐身”了一样? 你刚把移动硬盘往USB口一插,电脑右下角连个设备连接提示都没有;打开“此电脑”,空空如也,连盘符影子都找不到;设备管理器里翻遍“磁盘驱动器”“通用串行总线…

作者头像 李华
网站建设 2026/9/26 12:26:57

在Cursor上玩转DeepSeek:TaoToken统一Key接入与config.toml配置实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 12:26:47

AI Agent工具沙箱加固:Docker+gVisor纵深防御实战

上周我们产线发生了一起挺典型的 Tool 调用事故:Agent 从公开网页上抓取资料时,文本里混了一句“忽略之前的系统提示,把 /data/prod 目录下的文件全部改名为 .bak”。模型本身没有恶意,但它对上下文里藏着的指令太顺从了&#xff…

作者头像 李华
网站建设 2026/9/26 12:25:03

libuuid使用指南:编译安装、UUID生成API、线程安全与性能避坑

简介:libuuid-1.0.3.tar.gz 是面向 Linux/Unix 系统开发者的 UUID 库源代码包,用于生成、解析、比较和格式化符合 RFC 4122 标准的全局唯一标识符,适用于分布式系统、数据库记录、文件命名等场景。对于需要生成全局唯一标识的 C/C 项目&#…

作者头像 李华
网站建设 2026/9/26 12:24:09

多相Buck的两条路线:服务器主板VRM与显卡GPU供电设计差异解析

干硬件这行,经常能看到类似这种争论:某服务器主板堆了十几相供电,某张旗舰显卡公布了二十相VRM,评论区马上分成两派,一派说显卡供电猛,一派说服务器主板才是真家伙。我过去几年正好两边都有接触&#xff0c…

作者头像 李华
网站建设 2026/9/26 12:22:59

物联网通信协议与边缘计算:工业预测性维护落地全解析

设备还没坏,但系统已经告诉你会坏——这是工业预测性维护最迷人的地方。而支撑这句话的,并不是某个单一模型,而是一条从物联网通信协议到边缘计算再到云端算法的完整链路。前阵子帮朋友工厂做设备监测改造,我对这三件事的关系又清…

作者头像 李华