group by + count(*) 是最高效可读的分类统计解法,优先用 category_id 而非 name 分组,确保索引覆盖,大表需物化统计表,null 分类需按业务逻辑处理。

直接用 GROUP BY + COUNT(*) 就够了,别绕弯写子查询
绝大多数场景下,COUNT(*) 配合 GROUP BY 是最高效、最可读的解法。数据库引擎对这种聚合做了深度优化,特别是当分类字段(比如 category_id)上有索引时,执行计划通常能走索引扫描甚至索引覆盖,避免回表。
常见错误是先写个子查询统计每个 SKU 的归属再聚合,或者用 COUNT(DISTINCT sku_id) ——除非你明确要排重,否则 COUNT(*) 更快、更准确(SKU 表主键唯一,重复行本身就不该存在)。
- 确保
category_id字段有索引,尤其是大表(千万级 SKU);没索引时GROUP BY可能触发 filesort 或临时表 - 如果分类数据来自关联表(如
categories),用LEFT JOIN要小心:空分类会出现在结果里但COUNT(*)仍为 0,符合业务预期;若只想要有 SKU 的分类,改用INNER JOIN - 避免在
GROUP BY字段上套函数,比如GROUP BY UPPER(category_name),会强制全表扫描
区分「逻辑分类」和「物理分类字段」,别盲目 GROUP BY name
电商中常把分类名(category_name)当分组依据,但实际应优先用 category_id。名称可能重复(如不同父类下都有“手机壳”)、带空格或大小写不一致,导致统计结果分裂。
如果必须按名称统计,先确认 category_name 是否在业务上具备唯一性;否则得连同 parent_id 或 category_path 一起分组,否则数据会错。
-
GROUP BY category_id是安全默认项;GROUP BY category_name是风险操作 - 若前端展示需名称,用
JOIN categories ON t.category_id = c.id关联查名称,别在分组时依赖字符串字段 - 注意 MySQL 5.7+ 默认开启
sql_mode=ONLY_FULL_GROUP_BY,SELECT 中非聚合字段必须出现在 GROUP BY 里,否则报错Expression #1 of SELECT list is not in GROUP BY clause
大表分页统计时,COUNT(*) 会变慢?试试物化统计表
当 SKU 表超 5000 万行且需高频查询各分类总数时,每次 GROUP BY 都扫一遍数据确实拖慢响应。这时实时统计已不现实,得换思路。
核心不是优化 SQL,而是改变统计模式:用定时任务(如每小时)把结果写入一张 category_sku_count 表,查询直接读这张小表。更新时只增量计算变化部分(如监听商品上下架事件),比全量重算更稳。
- 物化表结构只需
category_id和sku_count两列,加唯一索引 - 避免用触发器自动维护——高并发写入易成瓶颈;推荐用应用层或消息队列异步更新
- 若临时需要精确值(比如运营核对),再跑一次
SELECT category_id, COUNT(*) FROM skus GROUP BY category_id,但不作为常态接口
注意 NULL 分类的处理,它会被单独归为一组
如果部分 SKU 的 category_id 为 NULL,GROUP BY category_id 会把它们聚合成一行,category_id 值显示为 NULL,COUNT(*) 统计这部分数量。这未必符合业务预期——有些系统要求未分类 SKU 不计入任何分类总数。
- 想排除未分类 SKU:加
WHERE category_id IS NOT NULL - 想单独标记未分类:保留
GROUP BY,但应用层识别category_id IS NULL这一行做特殊处理 - 检查数据质量:长期存在大量
NULL分类,说明上游录入或同步流程有问题,得从源头修复
分类维度看似简单,但 ID 设计、空值语义、索引覆盖、统计时效性这几个点卡住,性能和结果就容易翻车。别只盯着 SQL 写法,先看清数据怎么来的、业务到底要什么数。











