MySQL内存临时表大小由tmp_table_size与max_heap_table_size二者较小值决定,必须设为相同值(如64M~256M),否则仍会因阈值不一致触发磁盘落盘,导致性能下降和OOM风险。

MySQL 不会在执行 SQL 前预分配临时内存空间;它只在真正需要时,按需动态分配 —— 而且分配时机、大小和是否溢出到磁盘,全由运行时查询特征和会话级参数共同决定。
tmp_table_size 和 max_heap_table_size 怎么协同控制内存临时表
这两个参数共同决定了单个会话中「内存临时表」的最大容量。只要临时表大小 ≤ 二者中的较小值,就留在内存(MEMORY 引擎);一旦超过,就会转为磁盘临时表(InnoDB 或以前的 MyISAM)。
-
tmp_table_size是会话级变量,可动态SET,但仅对后续语句生效;它不控制已存在的临时表 -
max_heap_table_size同样是会话级,且必须 ≥tmp_table_size,否则 MySQL 会静默取小值 - 二者默认都是 16MB(5.7/8.0),但
max_heap_table_size还影响CREATE TEMPORARY TABLE ... ENGINE=MEMORY的建表上限 - 注意:即使你
SET tmp_table_size = 256M,若max_heap_table_size仍是 16M,实际阈值还是 16M
哪些 SQL 操作会触发临时表内存分配
不是所有查询都走临时表,只有特定逻辑路径才会。关键看执行计划里是否出现 Using temporary。
-
GROUP BY:无合适索引覆盖分组字段时,必须建临时表聚合 -
ORDER BY+LIMIT组合:若排序无法用索引完成,且结果集大,可能先排全量再截断 → 触发临时表 -
DISTINCT:等价于GROUP BY,同样依赖索引;没索引就建临时表去重 -
UNION(非UNION ALL):要去重,必须用临时表合并+去重 -
JOIN中的“被驱动表”若无索引,且连接方式是 Block Nested-Loop,则可能用join_buffer_size,但不走临时表;只有涉及中间结果物化(如派生表、CTE)才可能触发
为什么 SET 了参数却没生效?常见失效场景
参数设对了,但查询依然用了磁盘临时表,大概率是以下某个环节卡住了:
- 你在全局 scope 设置:
SET GLOBAL tmp_table_size = 268435456,但已有连接不会继承,必须重新连或显式SET SESSION - 查询本身使用了 BLOB/TEXT 字段:只要 SELECT 列或 GROUP BY 列含这类类型,MySQL 强制走磁盘临时表,无视
tmp_table_size - 字符集或排序规则不一致(比如 JOIN 两边 collation 不同),导致无法用索引,又因字段长度超限被迫落盘
- 优化器判定「即使够内存,也不如直接走磁盘 InnoDB 临时表稳定」——尤其在 8.0.16+,InnoDB 临时表比 MEMORY 更可靠(支持崩溃恢复、行锁等)
如何确认某条 SQL 是否用了内存临时表
不能只看 EXPLAIN,得结合实际执行过程验证:
- 执行前打开状态统计:
FLUSH STATUS;,然后跑 SQL,再查:SHOW STATUS LIKE 'Created_tmp%';。重点关注:Created_tmp_tables(总临时表数)、Created_tmp_disk_tables(磁盘临时表数)。若后者 > 0,说明至少有一个落盘了 - 用
EXPLAIN FORMAT=TREE(8.0.16+)看执行树,找-> Using temporary并观察是否带on disk标记 - 开启 optimizer_trace:
SET optimizer_trace="enabled=on";,执行后查information_schema.OPTIMIZER_TRACE,搜索"using_temporary_table"字段,它会明确写出「reason: "Memory table is full"」或类似提示
最常被忽略的一点:临时表内存分配不是“SQL 开始前一次性划好一块”,而是随着数据写入逐步增长,直到达到阈值或语句结束。所以即使 sort_buffer_size 或 join_buffer_size 很大,也救不了 GROUP BY 场景下因字段多、行宽大导致的内存临时表快速溢出 —— 此时真正起作用的是那两个 _size 参数,而不是 buffer 类参数。


















