GROUP BY 配合聚合函数是汇总查询的唯一合理路径
直接在 SELECT 中写
COUNT()、
SUM()、
AVG()而不加
GROUP BY,只会返回单行结果——这适合统计全表,但无法按维度分组汇总。真要“按部门算人数”“按日期算销售额”,
GROUP BY不可省略,且必须包含所有非聚合字段。
常见错误:
SELECT dept, name, COUNT(*) FROM emp GROUP BY dept会报错(MySQL 5.7+ 严格模式下),因为
name既没参与分组也没被聚合。正确写法是只选分组键和聚合表达式:
SELECT dept, COUNT(*) AS cnt FROM emp GROUP BY dept。 MySQL 8.0+ 支持
GROUP BY后用序号引用 SELECT 列(如
GROUP BY 1),但可读性差,不建议在生产中使用 聚合字段别名不能在
GROUP BY中直接引用(如
GROUP BY cnt报错),必须写原始表达式或列位置 含
GROUP BY的查询默认隐式排序,但 MySQL 8.0+ 已取消该行为,需显式加
ORDER BY才能保证顺序
WHERE 和 HAVING 的分工必须分清
WHERE过滤的是「行」,在分组前生效;
HAVING过滤的是「组」,只能用聚合结果做条件。想查“订单数超 100 的客户”,必须用
HAVING COUNT(*) > 100,写在
WHERE里会报错或逻辑错误。
典型误用:
SELECT user_id, COUNT(*) FROM orders WHERE COUNT(*) > 100 GROUP BY user_id——
COUNT(*)在
WHERE阶段还不存在,直接语法错误。
WHERE可用索引加速,
HAVING无法走索引,性能更敏感,应尽量把能前置的条件挪到
WHERE时间范围筛选(如
created_at >= '2024-01-01')务必放在
WHERE,否则全表分组后再过滤,浪费大量计算
HAVING中支持复杂表达式,比如
HAVING AVG(price) * COUNT(*) > 5000,但要注意字段作用域仅限于当前分组结果
避免在聚合查询中 SELECT 大字段或 JOIN 冗余表
一旦用了
GROUP BY,MySQL 必须为每组暂存中间结果。如果 SELECT 中包含
TEXT、
BLOB字段,或 JOIN 了大表却只取其中几个字段,会显著放大临时表体积,甚至触发磁盘临时表(
Created_tmp_disk_tables增多)。
例如:统计每个用户的最新订单时间,错误写法是
SELECT u.name, o.* FROM users u JOIN orders o ON u.id = o.user_id GROUP BY u.id ORDER BY o.created_at DESC—— 这不仅语义错误(
o.*无法确定取哪一行),还会加载全部订单字段。 优先用子查询或窗口函数(MySQL 8.0+)替代多表聚合:比如用
(SELECT MAX(created_at) FROM orders o2 WHERE o2.user_id = u.id)替代 JOIN 确认
GROUP BY字段上有索引,尤其是复合索引要匹配分组顺序(如
GROUP BY status, date对应索引
(status, date)) 用
EXPLAIN FORMAT=TREE(8.0+)或
EXPLAIN观察是否出现
Using temporary; Using filesort,这是性能瓶颈信号
COUNT(*) 和 COUNT(字段) 的实际差异远不止“是否忽略 NULL”
COUNT(*)统计行数,InnoDB 可利用二级索引最小叶子节点快速估算(某些场景甚至不扫数据页);而
COUNT(字段)必须检查该字段是否为 NULL,即使字段有索引,也大概率要回表或扫描完整索引。
更隐蔽的问题:当字段允许 NULL 且业务逻辑依赖精确非空计数时,
COUNT(col)是对的;但若只是要“记录总数”,用
COUNT(*)不仅语义清晰,还能触发优化器更激进的执行策略。 MyISAM 表上
COUNT(*)是 O(1),但 InnoDB 没有全局行数缓存,仍需遍历索引
COUNT(1)和
COUNT(*)在 MySQL 中完全等价,无需刻意替换 对大表做
COUNT(*)时,若允许误差,可查
information_schema.TABLES中的
TABLE_ROWS(仅近似值,且 MyISAM 准确、InnoDB 不可靠)
聚合查询的性能拐点往往不在函数本身,而在分组键的选择、索引覆盖程度和中间结果集大小。一个没加索引的
GROUP BY字段,比写错十个聚合函数影响都大。
