MySQL 8.0+启用ONLY_FULL_GROUP_BY后,SELECT非聚合字段必须在GROUP BY中或被聚合函数包裹;WHERE过滤行,HAVING过滤组;多字段分组顺序影响分组结构;无索引GROUP BY易引发临时表与文件排序。

GROUP BY 后必须出现在 SELECT 列表中,或被聚合函数包裹
MySQL 8.0+ 默认启用 sql_mode=ONLY_FULL_GROUP_BY,这意味着如果用了 GROUP BY,SELECT 中的非聚合字段必须是 GROUP BY 的列。否则会报错:Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column。
常见误写:
SELECT id, name, COUNT(*) FROM users GROUP BY dept_id;
这里 id 和 name 不在 GROUP BY 中,也不带聚合函数,直接报错。
- 正确做法:只选分组依据列或聚合值,例如
SELECT dept_id, COUNT(*) FROM users GROUP BY dept_id; - 若真要查每个部门的某个人名(如任意一个),用
MAX(name)或MIN(name)—— 注意这不是“取第一条”,而是字典序最大/最小 - 想取真实某条记录(如最新注册者),
GROUP BY本身做不到,得配合窗口函数或子查询
WHERE 和 HAVING 的分工不能颠倒
WHERE 过滤的是“行”,在分组前执行;HAVING 过滤的是“组”,在 GROUP BY 之后、聚合计算完成才生效。把条件放错位置会导致逻辑错误或语法报错。
- 查“订单数超过 5 的用户”:必须用
HAVING COUNT(*) > 5,写在WHERE里会报错,因为COUNT(*)是聚合结果,还没产生 - 查“2024 年下单的用户中订单数超 5 的人”:时间条件放
WHERE order_time >= '2024-01-01',数量条件放HAVING COUNT(*) > 5 -
HAVING可以引用别名(如HAVING cnt > 5),但部分旧版 MySQL 不支持,稳妥起见还是写完整表达式
GROUP BY 多字段时顺序影响结果结构
多字段分组等价于“先按第一个字段分大组,再在每个大组内按第二个字段细分”。顺序不同,分组粒度和结果行数可能完全不同。
-
GROUP BY status, category:先分状态(如 pending/done),再各自内部按品类细分 -
GROUP BY category, status:先分品类,再各品类下按状态细分 —— 表头顺序、排序倾向都不同 - 如果后续要 JOIN 或导出到报表,字段顺序不匹配可能导致对不上维度
- 注意 NULL 值会被当作独立一组,多个 NULL 会合并成一条,这点和普通 WHERE 判定不同
性能隐患:没索引的 GROUP BY 会触发临时表 + 文件排序
当 GROUP BY 字段没有索引,或者索引无法覆盖(比如用了函数或表达式),MySQL 往往要创建临时表并排序,数据量稍大就明显变慢。
- 确认是否走索引:用
EXPLAIN看type是否为index或range,且Extra不含Using temporary; Using filesort - 复合索引要注意最左前缀:若常
GROUP BY a, b,建索引应为INDEX(a, b),而非单独两个单列索引 - 避免在
GROUP BY中用函数,如GROUP BY DATE(create_time)会失效索引;可改用范围查询预过滤,再分组
真正难处理的不是语法,而是分组后还要取原记录详情——这时候 GROUP BY 已经不够用了,得切到窗口函数或关联子查询。










