Using temporary 和 Using filesort 同时出现,说明 GROUP BY 查询因索引错配导致磁盘临时表和排序,根因是复合索引未按 WHERE 条件和 GROUP BY 字段顺序正确设计。

直接结论:Using temporary 和 Using filesort 同时出现,基本等于 GROUP BY 查询正在用磁盘 IO 烤 CPU——不是慢,是失控。根因几乎总是索引没对上,而不是数据量大。
GROUP BY 为什么非得建复合索引?
MySQL 的 GROUP BY 不是“分个组就完事”,它默认要先排序再分组。如果分组字段 city 上只有单列索引,但查询还带了 WHERE order_time >= '2026-04-01',那这个单列索引大概率被跳过——因为过滤后的数据在物理存储上并不按 city 有序,MySQL 只能捞出全部匹配行,再建临时表、排序、分组。
- 必须用复合索引,且顺序严格匹配
GROUP BY字段顺序:比如GROUP BY dept_id, status,索引就得是(dept_id, status),反过来或缺一不可 - 如果还有
WHERE条件,把过滤字段放索引最左:例如WHERE status = 'paid' GROUP BY dept_id,索引应为(status, dept_id) -
SELECT中的非聚合列(如SELECT dept_id, name, COUNT(*))也得包含在索引里,否则仍要回表,可能让优化器弃用该索引
怎么一眼看出索引有没有生效?
别猜,用 EXPLAIN 看三处:
-
type别是ALL:那是全表扫描,索引完全没用上 -
key字段得有值:显示实际命中的索引名,为空说明没走索引 -
Extra里不能有Using temporary或Using filesort;若两者共存,就是典型“索引错配”信号
特别注意:即使 key 显示用了索引,但 rows 高得离谱(比如预估扫描 500 万行),说明索引虽然走了,但没起到过滤作用——很可能是 WHERE 条件字段不在索引最左,或用了函数(如 WHERE DATE(create_time) = '2026-07-01')导致索引失效。
ORDER BY NULL 是不是万能解药?
不是。它只解决一个具体问题:GROUP BY 默认会按分组字段隐式排序,如果你不需要这个顺序,加 ORDER BY NULL 能砍掉一次额外排序,避免 Using filesort。但它救不了 Using temporary。
- 适用场景:报表后台导出、API 返回后端自行排序、前端分页展示不依赖 DB 排序
- 不适用场景:结果要按分组字段升序/降序展示,且你又没建对应方向的索引(MySQL 5.7 不支持降序索引,
ORDER BY dept_id DESC仍会触发Using filesort) - 别滥用:加了
ORDER BY NULL却还在SELECT里写*或一堆非聚合字段,临时表照样膨胀,内存打满后照样写磁盘
临时表参数调优只是兜底,不是正解
tmp_table_size 和 max_heap_table_size 调大,确实能让更多临时表留在内存里,避免磁盘 IO。但这治标不治本——3000 万行分组,哪怕全在内存里建临时表,CPU 和内存带宽也扛不住。
- 仅当确认索引已最优、数据量无法再过滤、且临时表确实卡在内存阈值时,才考虑微调这两个参数
- 调太高风险明显:多个并发查询同时撑爆内存,触发 OOM Killer 杀进程
- 真正有效的“缩表”手段是:用
WHERE先过滤(比如按天分区 + 时间条件)、用覆盖索引减少字段体积、必要时预建汇总表(如每小时订单数存到orders_hourly_summary)
最常被忽略的一点:分组键基数过高。比如 GROUP BY order_time(精确到秒),千万级订单会生成千万个分组,索引再好,分组过程本身也慢——这时不是索引问题,是设计问题,得改聚合粒度(DATE(order_time))或换架构(物化视图、实时 OLAP)。


















