看到Copying to tmp table应先用EXPLAIN检查type、key和Extra字段是否显示全表扫描或Using temporary/Using filesort,再通过SHOW GLOBAL STATUS确认Created_tmp_disk_tables是否增长以判断是否落盘,根本优化是为JOIN、WHERE、GROUP BY、ORDER BY涉及字段建立联合索引。

看到Copying to tmp table就该查EXPLAIN
这个状态本身不报错,但说明MySQL正在把中间结果写进临时表——关键得先判断是内存临时表还是磁盘临时表。直接执行EXPLAIN看执行计划,重点盯三处:type是否为ALL或index(全表/索引扫描),key是否为NULL(没走索引),Extra里有没有Using temporary或Using filesort。只要出现后两者,基本就是索引缺失导致优化器被迫建临时表做排序或分组。
别急着调tmp_table_size,先看Created_tmp_disk_tables
执行SHOW GLOBAL STATUS LIKE 'Created_tmp_disk_tables',记下当前值;跑一遍慢查询,再查一次。如果差值明显上涨,说明临时表已落盘,I/O开销拉满。此时再查SHOW VARIABLES LIKE 'tmp_table_size'和SHOW VARIABLES LIKE 'max_heap_table_size',取二者较小值——这就是单个查询能用的最大内存临时表容量。默认常为16MB和128MB,对含GROUP BY、ORDER BY或多表JOIN的查询极易触发落盘。
-
max_heap_table_size还管MEMORY引擎表上限,不能只调tmp_table_size -
SET GLOBAL修改后新连接才生效,线上调大前必须确认空闲内存充足,否则可能OOM - 单纯调大参数只是掩盖症状,不解决根本问题
联合索引要覆盖JOIN、WHERE、ORDER BY字段
比如查询SELECT * FROM t LEFT JOIN r ON t.order_no = r.order_no WHERE r.status = 1 ORDER BY t.gmt_modified DESC,若r.order_no和r.status没联合索引,t.gmt_modified也没索引,MySQL就得先把所有匹配行捞出来,再排序截断——必然走临时表。正确做法是建两个索引:r(order_no, status)覆盖关联和过滤,t(gmt_modified)让ORDER BY能直接利用索引有序性。
-
ON条件字段、WHERE字段、GROUP BY字段、ORDER BY字段,只要参与中间结果生成逻辑,就应优先考虑联合索引覆盖 - 避免在
WHERE里对字段做函数操作(如WHERE DATE(create_time) = '2024-01-01'),这会让索引失效 -
EXPLAIN中rows值过大,往往意味着索引没选对或缺失,不是数据量问题
临时表是手段,不是目标
手动建CREATE TEMPORARY TABLE ... SELECT确实能把结果存下来,但这是为后续复杂处理服务的,和优化“Copying to tmp table”状态完全不是一回事。后者是优化器被迫为之的性能退化路径,前者是你主动控制的数据暂存方式。真遇到需要多次复用中间结果的场景,可以建临时表,但前提是原查询本身已经足够快——否则只是把慢动作拆成两步而已。
最容易被忽略的是:同一个字段在WHERE和ORDER BY里都出现时,单列索引可能不够,得看实际执行计划里key_len和ref是否合理;还有LEFT JOIN里被驱动表(右表)的索引,比驱动表(左表)的索引更容易被忽视。


















