group by + order by 会触发两次独立排序,mysql 8.0+ 不复用分组排序结果,需建匹配列序及方向的联合索引(如 group by a,b order by b desc 需 index(a,b desc)),否则引发 using temporary 和 using filesort 或双重 sort 节点,导致磁盘临时表激增。

GROUP BY + ORDER BY 触发双重排序溢出
MySQL 或 PostgreSQL 执行 GROUP BY 后再 ORDER BY,实际会做两次独立排序:一次为分组聚合准备(哈希或排序),一次为最终结果排序。若两者都无索引支撑,Sort_merge_passes 和 Created_tmp_disk_tables 会同时飙升,磁盘 I/O 瞬间拉满。
常见错误现象:EXPLAIN 显示 Using temporary; Using filesort 并存;PostgreSQL 的 EXPLAIN ANALYZE 中出现两个 Sort 节点,且第二个的 Sort Method: external merge。
- MySQL 8.0+ 不会自动复用
GROUP BY的排序结果给外层ORDER BY,哪怕字段顺序完全一致 - PostgreSQL 13+ 可复用,但仅限于
ORDER BY字段完全匹配GROUP BY的列+方向,多一个DESC或少一个都失效 - 避免写
SELECT * FROM (...) t ORDER BY t.group_col DESC,而应把ORDER BY提前到子查询里,并确保该子查询已走索引
为什么建了索引还是落盘?看懂执行计划里的陷阱
索引存在 ≠ 排序走索引。关键要看 EXPLAIN 中 Extra 列是否出现 Using index for group-by(MySQL)或 GroupAggregate 前是 Index Scan(PostgreSQL)。否则,索引只是被用来过滤,排序仍走内存缓冲区。
容易踩的坑:
- 复合索引顺序错:要支持
GROUP BY a, b ORDER BY b DESC,必须建INDEX(a, b DESC),而非INDEX(b, a) - 字段类型隐式转换:
GROUP BY VARCHAR却用INT常量比较,索引失效,排序被迫全量加载 - 表达式干扰:写
GROUP BY UPPER(name),即使name有索引,也需额外建函数索引,否则直接进临时表
tmp_table_size 和 sort_buffer_size 到底该调谁?
GROUP BY 内存溢出跟 sort_buffer_size 无关——它只管 ORDER BY 和 DISTINCT 的排序缓冲,不参与哈希聚合。真正控制分组内存的是 tmp_table_size 和 max_heap_table_size(MySQL)或 work_mem(PostgreSQL)。
实操要点:
- MySQL 必须同时设
tmp_table_size = 536870912和max_heap_table_size = 536870912,否则取小值生效 - PostgreSQL 改
work_mem要谨慎:SET LOCAL work_mem = '128MB'比全局设更安全,避免高并发下 OOM - 调参前先确认是否真因内存不足:PostgreSQL 出现
HashAgg (Batches: 2),MySQL 出现Sort_merge_passes > 0且Sort_scan很高,才是调参信号
比索引和参数更根本的解法:砍掉不需要的分组键
很多慢查询不是因为没索引,而是业务逻辑本身在要求数据库对千万级唯一值做分组——比如 GROUP BY long_url 或 GROUP BY json_blob。这时再大的内存、再好的索引也扛不住。
真实可行的替代方案:
- 用哈希代替原始值:
GROUP BY SHA2(long_text, 256),固定 32 字节,冲突率可控 - 提取确定性前缀:
GROUP BY SUBSTRING_INDEX(url, '/', 3),但必须配合函数索引(MySQL 8.0+ 支持) - 拆分聚合:先按主键范围分片查,再应用层合并结果,例如每次
WHERE id BETWEEN 100000 AND 200000 GROUP BY x - 删掉
SELECT *:只选分组字段和聚合字段,避免大字段拖垮临时表单行体积
最常被忽略的一点:WHERE 条件一定要写在 GROUP BY 之前,而不是包在视图或 CTE 里——否则数据库先聚全表,再过滤,99% 的工作白做。










