mysql内存临时表实际上限取tmp_table_size与max_heap_table_size的较小值,二者不一致会导致频繁落盘;触发磁盘临时表的真正条件包括blob/text字段、未索引的函数/表达式排序、显式指定引擎等,而非单纯超内存。

tmp_table_size 和 max_heap_table_size 不一致导致内存临时表被截断
MySQL 用 tmp_table_size 控制内存临时表上限,但真正起作用的是它和 max_heap_table_size 中的较小值。两者不等时,系统会隐式取小值,你设了 512M 却发现 Created_tmp_disk_tables 还在涨,大概率是另一个参数卡在 64M 没同步改。
- 必须在
my.cnf的[mysqld]段里显式写两行:tmp_table_size = 256M和max_heap_table_size = 256M - 重启前务必执行
SHOW VARIABLES LIKE 'tmp_table_size';和SHOW VARIABLES LIKE 'max_heap_table_size';确认两者数值完全相等 - 如果实例同时跑 OLTP 和报表查询,建议按单条峰值报表 SQL 的中间结果大小来定(比如观察
EXPLAIN FORMAT=JSON中的estimated_row_count× 平均行宽),但别超物理内存的 20%
临时表引擎从 MEMORY 切到 MyISAM/InnoDB 的真实触发条件
很多人以为“只要超内存就自动切引擎”,其实不是。MySQL 只在满足以下任一条件时,才会放弃 MEMORY 引擎、改用磁盘表(MyISAM 或 InnoDB):
- 当前语句涉及 BLOB/TEXT 字段 —— MEMORY 引擎根本不支持,直接落盘
- 查询中用了
GROUP BY或ORDER BY,且排序字段含函数(如UPPER(name))、表达式或生成列未建索引 - 创建临时表时显式指定了
ENGINE=MyISAM或ENGINE=InnoDB(少见,多见于手动CREATE TEMPORARY TABLE) -
innodb_file_per_table=OFF且临时表要走 InnoDB 时,可能因共享表空间不足被迫退回到 MyISAM
注意:SHOW CREATE TABLE 查不到内部临时表的引擎类型;只能靠 SHOW STATUS LIKE 'Created_tmp%' + 执行计划交叉验证。
为什么把 tmpdir 挂 SSD 也压不住磁盘 I/O?
换高速存储只是缓解手段,不是根治方案。如果 Created_tmp_disk_tables 持续上涨,说明语句本身还在高频生成大临时结果 —— SSD 只是让每次落盘快点,但并发一高,IOPS 依然打满。
- 先确认 MySQL 实际用的路径:
SELECT @@tmpdir;,再用df -h看对应挂载点是否真满了 - 用
lsof +D /path/to/tmpdir检查是不是其他进程(比如日志轮转脚本)在往同一目录写大文件 - 即使 tmpdir 在 SSD 上,也要确保
tmp_table_size和max_heap_table_size足够大,否则大量小临时表反复创建销毁,照样刷 I/O - 对长期运行的报表类连接,考虑加
SET SESSION sort_buffer_size = 8M;(别全局设太高,避免连接数多时内存爆炸)
GROUP BY / ORDER BY 导致磁盘临时表的隐蔽坑
最常被忽略的是字段类型隐式转换和 SELECT 列冗余。比如 GROUP BY user_id,但 WHERE 条件里写了 WHERE user_id = '123'(字符串),MySQL 会放弃索引,连带 GROUP BY 也失效,强制走磁盘临时表。
- 检查
EXPLAIN输出:若type是ALL或key为NULL,先修索引,别急着调内存参数 - 避免
SELECT name, COUNT(*) FROM t GROUP BY dept_id——name既没聚合也没分组,MySQL 可能随机取值,还大概率触发临时表 - 如果必须用函数排序,比如
ORDER BY DATE(created_at),优先建生成列:ALTER TABLE t ADD COLUMN created_date DATE AS (DATE(created_at)) STORED;,再对created_date建索引
临时表是否落盘,不取决于“有没有用临时表”,而取决于“有没有 on disk”。很多优化只盯着 Using temporary,却漏看执行计划里那一行不起眼的 on disk 标记。











