必须将tmp_table_size与max_heap_table_size设为相等值(如均为64m),因mysql取二者较小值作为内存临时表硬性上限,不一致会导致意外落盘、created_tmp_disk_tables激增及oom风险。

tmp_table_size 和 max_heap_table_size 必须设相等且显式限制
这两个参数控制 MySQL 在内存中创建临时表的上限,但很多人只调其中一个,导致行为不可控。它们不是“建议值”,而是硬性阈值:只要任一值被突破,MySQL 就会退化为磁盘临时表(Created_tmp_disk_tables 增长),而更危险的是——如果两者不一致,MySQL 取较小者生效,但你根本不知道哪次查询卡在哪个边界上。
常见错误现象:Created_tmp_disk_tables 持续上升,但 Created_tmp_tables 却没明显变化;或者某条 GROUP BY 查询突然变慢几秒,EXPLAIN FORMAT=JSON 显示 "using_temporary_table": true 但没报错。
- 线上安全值范围是
64M~128M,8GB 内存机器起步用64M,16GB 可设128M,但绝不能超过可用内存的 20%~30% - 必须同时设置:
tmp_table_size = 64M和max_heap_table_size = 64M(注意单位是M,不是MB) - 动态修改只影响新连接:
SET GLOBAL tmp_table_size = 67108864;、SET GLOBAL max_heap_table_size = 67108864;(64MB = 67108864 字节) - 配置文件中写进
[mysqld]段才持久,重启后生效;否则 MySQL 重启就回退到默认16M
per-connection 内存开销必须纳入总预算计算
每个连接都会独占一份 sort_buffer_size、join_buffer_size、read_buffer_size 等缓冲区,而这些加起来可能比 innodb_buffer_pool_size 还吓人。比如 max_connections = 300,sort_buffer_size = 4M,光排序缓冲就可能吃掉 1.2GB —— 这还没算临时表、线程栈和连接对象本身。
容易踩的坑:把 innodb_buffer_pool_size 设成 6GB,以为“留了 2GB 给系统”,结果并发上来瞬间 RSS 超过 10GB,OOM Killer 直接干掉 mysqld。
-
sort_buffer_size和join_buffer_size建议保持默认或设为2M~4M,不要按“最大可能”去配 - 检查当前连接实际开销:
SELECT THREAD_ID, PROCESSLIST_USER, PROCESSLIST_HOST, VIRTUAL_MEMORY_USED FROM performance_schema.memory_summary_by_thread_by_event_name WHERE EVENT_NAME = 'statement/sql/select' ORDER BY VIRTUAL_MEMORY_USED DESC LIMIT 5;(需开启 performance_schema) - 估算理论峰值内存:
innodb_buffer_pool_size + (sort_buffer_size + join_buffer_size + read_buffer_size) × max_connections,这个总和必须明显低于容器--memory或系统free -h显示的可用内存
禁用 swap 时必须压低临时表上限
云主机普遍关闭 swap,本意是避免 IO 拖慢响应,但副作用极强:一旦内存不足,OOM Killer 不会等 swap,而是立刻杀掉 RSS 最大的进程——通常是 mysqld。而大查询建隐式内存临时表时,就靠 tmp_table_size 和 max_heap_table_size 兜底。默认 16M 在复杂 JOIN 或多字段 GROUP BY 下几秒就撑爆。
性能影响很隐蔽:单个查询看似没报错,但大量并发执行时,多个连接同时申请内存临时表,叠加后瞬间耗尽余量,触发 OOM。
- 禁用 swap 的机器,这两个值建议统一设为
64M,而非128M或更高 - 验证是否生效:执行
SHOW VARIABLES LIKE 'tmp_table_size';和SHOW VARIABLES LIKE 'max_heap_table_size';,确认返回值一致且单位正确 - 监控关键指标:
SHOW GLOBAL STATUS LIKE 'Created_tmp%tables';,若Created_tmp_disk_tables持续增长,说明内存临时表已不够用,该优化 SQL 而不是加内存
容器部署下必须用 cgroup v2 硬限整个 mysqld 进程
只在 my.cnf 里调小 innodb_buffer_pool_size 没用。MySQL 进程看不到 cgroup 边界,仍会按宿主机内存估算 buffer pool,导致申请超限被 kill。而且默认 cgroup v2 下,OOM 时不给机会降级,直接终止进程。
最常被忽略的一点:Kubernetes 的 resources.limits.memory 是软限,MySQL 启动时不会感知,仍可能分配超限内存。
- 必须显式启用
oom_kill_disable=0(即允许 OOM Killer 工作),并配合MemoryMax(systemd)或--memory(docker)设硬上限 - 验证是否生效:
cat /sys/fs/cgroup/memory/my-mysql/memory.max(cgroup v2 路径),确认值与你设定一致 - 启动后立刻检查:
ps -o pid,vsz,rss,comm -C mysqld,RSS 必须稳定低于 cgroup limit,否则说明配置未落地或有其他进程共享同一 cgroup
tmp_table_size 和 max_heap_table_size 的数值本身不难设,难的是把它和 max_connections、sort_buffer_size、cgroup 限制串成一条链——少环一环,OOM 就随时可能来。











