COUNT(*)统计每组总行数(含NULL),COUNT(字段)仅统计该字段非NULL的行数;前者用于统计记录总数,后者用于统计某字段有效值数量。

GROUP BY 后 COUNT(*) 和 COUNT(字段) 的区别必须分清
统计总数时,COUNT(*) 和 COUNT(字段名) 行为完全不同:COUNT(*) 统计行数(包括 NULL),而 COUNT(字段名) 会跳过该字段为 NULL 的行。如果类别字段本身允许为空(比如 category 是 NULLABLE),用 COUNT(category) 会导致空类别被忽略——这不是“漏数据”,是语义正确但不符合你“每个类别的记录总数”的原始需求。
常见错误现象:结果里看不到 NULL 类别,或总数对不上 SELECT COUNT(*) FROM table。
- 要包含所有分组(含
NULL类别),一律用COUNT(*) - 只有当你明确想排除某字段为
NULL的行时,才用COUNT(字段名) - 性能上,
COUNT(*)在 InnoDB 中通常比COUNT(字段名)略快(尤其字段非主键且无索引时)
WHERE 和 HAVING 混用导致过滤逻辑错乱
新手常把条件写错位置:用 HAVING 过滤原始数据(比如 HAVING category = 'book'),或用 WHERE 过滤聚合结果(比如 WHERE COUNT(*) > 100)。这要么报错,要么结果诡异。
正确分工:
-
WHERE在分组前过滤行(如WHERE status = 'active') -
HAVING在分组后过滤组(如HAVING COUNT(*) >= 50) - 如果既要限定类别范围又要筛出高频类别,两个都得用:
WHERE category IS NOT NULL+HAVING COUNT(*) > 10
没加索引时 GROUP BY 可能全表扫描+临时表
执行 EXPLAIN SELECT category, COUNT(*) FROM products GROUP BY category,如果 type 是 ALL 或 Extra 出现 Using temporary; Using filesort,说明 MySQL 正在内存/磁盘建临时表排序分组——数据量一过十万就明显变慢。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
优化关键:给 GROUP BY 的字段加索引。
- 单列分组(如只按
category):建普通索引INDEX(category) - 带
WHERE条件的组合(如WHERE status='on' GROUP BY category):考虑联合索引INDEX(status, category),顺序不能颠倒 - 注意:主键索引不能替代分组字段索引,除非分组字段就是主键或主键前缀
统计结果里 NULL 类别显示为空字符串或丢失?
MySQL 默认把 NULL 分组显示为字面量 NULL,但某些客户端(如旧版 phpMyAdmin、部分 ORM)可能渲染成空字符串或直接跳过,造成“类别消失”的错觉。
验证和显式处理方式:
- 先执行
SELECT category, COUNT(*) FROM t GROUP BY category ORDER BY category IS NULL, category,确认NULL是否真存在 - 若需统一展示,用
COALESCE(category, '[unknown]')替换NULL值 - 注意:加了
COALESCE后索引仍有效,但若写成IFNULL(category, 'x') = 'x'就无法走索引
最易被忽略的是:分组字段是否真的允许 NULL,以及你的业务定义中,“未分类”算不算一个有效类别——这个判断不在 SQL 层,而在表设计和需求文档里。

















