innodb_buffer_pool_size不应设为物理内存80%,而应按可用内存的60%保守设置,小内存机器直接设1–1.5gb;需预留系统、其他进程及容器限制空间,并配合tmp_table_size等参数控制per-connection内存开销。

MySQL 的 innodb_buffer_pool_size 别设成物理内存的 80%
很多人一上来就查“MySQL 内存调优”,看到博客说“建议设为物理内存 70%–80%”,直接照搬,结果在容器或混部环境下 OOM 被杀。这不是配置错了,是没算清「谁在用内存」。innodb_buffer_pool_size 只管 InnoDB 缓存,但 MySQL 还有线程栈、排序缓冲区、临时表、连接对象、查询缓存(即使关了也有开销)、甚至插件和 UDF —— 这些加起来可能轻松吃掉 1–2GB。更关键的是:Linux 的 oom_score_adj 会让 MySQL 进程成为 OOM Killer 的首选目标。
- 真实可用内存 = 总内存 − 系统保留(至少 1GB)− 其他进程常驻内存(如 Redis、日志 agent)− 容器 memory limit(如果用了 cgroup v1/v2)
- 生产环境保守值:
innodb_buffer_pool_size≤ 可用内存 × 60%,小内存机器(≤4GB)直接设为 1GB 或 1.5GB,别碰 75% - 验证方法:启动后执行
SHOW ENGINE INNODB STATUS\G,看Buffer pool size和Database pages是否接近;再用ps -o pid,vsz,rss,comm -C mysqld观察 RSS 实际占用,必须明显低于 cgroup limit 或系统 free 内存
禁用 swap 时必须调低 tmp_table_size 和 max_heap_table_size
很多云主机默认关 swap,这本意是避免 IO 拖慢响应,但副作用是:一旦内存不足,OOM Killer 不会等 swap,而是立刻干掉最大 RSS 进程——通常是 mysqld。而大查询建隐式内存临时表时,就靠这两个参数兜底。默认值(16MB/16MB)在复杂 JOIN 或 GROUP BY 下几秒就能撑爆。
- 统一设为
tmp_table_size = 64M、max_heap_table_size = 64M(注意单位是M,不是MB),比默认高但远低于 buffer pool,避免单查询吃光余量 - 配合监控:查
SHOW GLOBAL STATUS LIKE 'Created_tmp%tables';,若Created_tmp_disk_tables持续增长,说明临时表已落地磁盘——这时该优化 SQL,而不是加内存 - 切记:这两个值是 per-connection 的,100 个并发连接理论上最多消耗 100 × 64MB = 6.4GB,必须纳入总内存预算
容器部署必须显式设置 --memory 并启用 oom_kill_disable=0
在 Kubernetes 或 Docker 里只配 resources.limits.memory 不够。MySQL 进程看不到 cgroup 边界,仍会按宿主机内存估算 buffer pool,导致申请超限被 kill。而且默认 cgroup v2 下,OOM 时不会写日志,排查只剩 dmesg | grep -i "killed process"。
- Docker 启动加参数:
--memory=4g --memory-reservation=3.5g --oom-kill-disable=false(注意不是true) - MySQL 配置中
innodb_buffer_pool_size必须硬编码为3g(即 memory-reservation 值 × 0.9),不能用百分比 - K8s 中务必配
livenessProbe检查mysqladmin ping,否则 OOM 后容器卡在 Terminating 状态,Service 流量还在往里导
performance_schema 开太多收集器会悄悄吃掉几百 MB
默认开启的 performance_schema 在高并发下本身就有内存开销,尤其当你启用了 events_statements_history_long 或一堆 xxx_history=ON 的消费者。它不走 buffer pool,而是从操作系统 malloc,且不释放——直到重启。
- 线上建议关闭非必要历史表:
UPDATE performance_schema.setup_consumers SET ENABLED = 'NO' WHERE NAME LIKE '%history%'; - 检查当前内存占用:
SELECT * FROM performance_schema.memory_summary_global_by_event_name ORDER BY CURRENT_NUMBER_OF_BYTES_USED DESC LIMIT 5;,重点关注memory/performance_schema/%行 - 如果不用 SQL 性能分析,直接在配置里加
performance_schema = OFF,省心又省内存
MySQL 的内存不是“设得越大越稳”,而是“留得越准越活”。最常被忽略的点是:buffer pool 只是冰山一角,per-connection 的临时结构、PS 的隐藏分配、容器的 cgroup 边界错觉——这些加起来,往往比你预估的多出 1–3GB。配完一定要用 ps 和 dmesg 对着看,而不是只信 SHOW VARIABLES。











