GROUP BY优化全解:如何写出既不丢数据又飞快的分组查询

📅 2026/6/26 17:09:59
GROUP BY优化全解:如何写出既不丢数据又飞快的分组查询
大家好我是小耶写功课只是为了我踩过的坑你们别再踩了前几周我们讲了执行计划、索引设计、COUNT优化、事务隔离级别今天来聊聊一个日常开发中使用频率极高、但也最容易出问题的话题​GROUP BY​。某天凌晨1点报表系统卡了半小时。执行计划里赫然写着Using where; Using temporary; Using filesort。这三个词凑在一起意味着MySQL在内存里建了张临时表把3000万行数据一行行插进去然后全表扫描排序最后返回10行。这不是SQL这是CPU烤机程序。很多人以为“GROUP BY不就是分个组吗”但实际上它的执行流程远比表面复杂。今天我们从执行机制出发把GROUP BY的性能陷阱一个一个拆开讲。先搞清楚GROUP BY到底在做什么执行一条GROUP BY查询时数据库做了三件事​排序​把数据按分组字段排序。只有相同值的行挨在一起才能分组统计。​分组​遍历排序后的结果遇到相同值累加遇到新值新开分组。​**可选再排序**​如果还有ORDER BY再做一次排序。问题的核心在于​第一步——排序​。如果分组字段上没有合适的索引MySQL无法直接按顺序遍历数据就只能建一张临时表把符合条件的行全插进去对临时表做filesort再遍历分组。3000万行数据先写临时表、再全表排序、再遍历——每一步都在烧CPU和磁盘I/O。Using temporary和Using filesort什么时候触发​触发Using temporary的规则​如果GROUP BY的列没有可用索引MySQL就必须先排序才能分组而排序需要空间于是建临时表。但“可用索引”比你以为的苛刻——不是有索引就行索引列的顺序决定一切。比如order_time有索引但city没有执行SELECT city, COUNT(*) FROM orders WHERE order_time 2026-04-01 GROUP BY city虽然order_time可以过滤数据但过滤后的数据按city分组时city没有索引仍然会触发临时表。​触发Using filesort的规则​当GROUP BY的结果需要排序比如ORDER BY cnt DESC且排序无法利用索引完成时就会触发filesort。索引优化的核心原则GROUP BY优化的根本思路是让分组字段走索引避免临时表和文件排序。原则一GROUP BY列必须形成索引的最左前缀MySQL使用索引进行GROUP BY时分组列必须是某个索引的最左前缀。如果表上有索引(c1, c2, c3)GROUP BY c1, c2可以利用索引但GROUP BY c2, c3不行——因为c2不是最左前缀。原则二索引顺序决定一切——WHERE条件中的范围查询是分水岭当WHERE条件中包含范围查询、、BETWEEN时索引的使用会受到限制。如果WHERE中有范围条件范围条件涉及的列必须放在索引的后面GROUP BY列必须前置。举例查询“2026年4月之后的订单按城市分组统计订单数”sqlSELECT city, COUNT(*) FROM orders WHERE order_time 2026-04-01 GROUP BY city;推荐索引(city, order_time)—— GROUP BY列city在前范围条件order_time在后。如果把索引建为(order_time, city)虽然order_time能过滤数据但过滤后的数据按city分组时city不在索引的前缀位置依然会触发临时表。原则三覆盖索引进一步提速如果索引不仅包含GROUP BY列还包含聚合函数需要的列查询可以直接从索引返回结果不需要回表。例如SELECT city, SUM(amount) FROM orders GROUP BY city索引(city, amount)可以让MySQL直接从索引完成分组和聚合。MySQL 8.0的两大优化优化一取消GROUP BY的隐式排序在MySQL 5.7及之前GROUP BY默认会按分组字段排序。如果不需要排序结果可以添加ORDER BY NULL来消除排序开销。MySQL 8.0取消了GROUP BY的隐式排序。如果确实需要排序必须显式写ORDER BY。这避免了不必要的排序开销是一个值得关注的性能优化点。优化二Loose Index Scan松散索引扫描这是MySQL使用索引处理GROUP BY的最高效方式。当GROUP BY列是索引的最左前缀时MySQL可以“松散地”扫描索引跳过不属于当前组的数据只读取每个组的第一个键值。举例表有索引(c1, c2, c3)查询GROUP BY c1, c2Loose Index Scan只需要读取每个(c1, c2)组合的第一行而不是扫描所有行。当没有WHERE条件时扫描的行数等于分组数远小于总行数。大数据量下的近似分组当数据量极大且业务允许误差时可以考虑近似分组方案​采样估算​对数据进行抽样如WHERE id % 100 0在样本上做GROUP BY再按比例放大结果。适用于趋势分析、快速报表。​预计算汇总表​对于固定维度的分组统计如每日、每小时的聚合可以定时计算并存入汇总表查询直接读汇总表。​分区表优化​在大数据量场景下结合分区表设计可以进一步减少数据扫描范围。GROUP BY 窗口函数的组合运用分组后计算占比、同比等场景GROUP BY和窗口函数可以组合使用-- 计算每个城市订单数占总订单数的比例 SELECT city, COUNT(*) AS city_cnt, COUNT(*) / SUM(COUNT(*)) OVER () AS ratio FROM orders GROUP BY city;窗口函数在分组后的结果集上计算不需要二次聚合比子查询写法更简洁高效。总结GROUP BY的性能问题根源往往在​索引设计​。理解临时表和文件排序的触发条件遵循“GROUP BY列前置、范围条件后置、覆盖索引收尾”的索引设计原则再配合MySQL 8.0的Loose Index Scan和取消隐式排序等新特性就能让分组查询从“CPU烤机程序”变成“秒级响应”。遇到大数据量且业务允许误差时采样估算和预计算也是值得考虑的方案。小耶在手SQL 不愁还有什么想了解的欢迎留言小耶一定知无不言言无不尽……我们下次见~