最快速通用方式是count()配group by;它一次扫描分组计数,比case或子查询更轻量,且count()计入null行,准确反映各状态值出现次数。

直接用 GROUP BY 配合 COUNT(*) 是最快速、最通用的方式,不需要额外函数或子查询,数据库引擎能直接走索引优化。
为什么不用 COUNT(CASE) 或多个子查询
当目标只是“每个状态值各出现多少次”,COUNT(*) + GROUP BY status 比 SUM(CASE WHEN ...) 更轻量:前者一次扫描分组计数,后者对每行都做条件判断;多个子查询或 UNION 会重复扫描表,IO 和 CPU 开销翻倍。只有当你需要「在同一行里并列展示 pending/shipped/completed 的数量」时,才考虑 CASE + SUM。
必须加 WHERE status IS NOT NULL 吗
看业务定义:
- 如果
status为NULL表示“状态未设置”,且你希望把它当作一种有效状态统计,就不要过滤 - 如果
NULL是脏数据或无效值,漏掉它会导致总数对不上——这时加WHERE status IS NOT NULL更安全 -
COUNT(*)会把NULL所在的整行计入,COUNT(status)则忽略该行;但你真正想统计的是“状态值的频次”,不是“非空行数”,所以一律用COUNT(*)
GROUP BY status 后没加 ORDER BY 会怎样
结果顺序不确定,不同数据库、不同执行计划下返回顺序可能变化。虽然某些 MySQL 版本看起来按字典序排,但这不是保证行为。要稳定输出,必须显式写:
SELECT status, COUNT(*) AS cnt FROM orders WHERE status IS NOT NULL GROUP BY status ORDER BY cnt DESC, status ASC;
这样高频状态在前,相同频次时按状态名升序排列,避免每次查出来顺序不一致导致应用层误判。
性能关键点:索引和数据分布
如果 status 只有 3–5 个固定值(如 'pending'/'shipped'/'done'),建单列索引效果有限,因为选择率太低;但如果有联合查询(比如按日期+状态筛选),建议建 (created_at, status) 联合索引,并确保 WHERE 条件顺序与索引字段顺序一致。另外,如果某状态占比超 90%(比如大量 'done'),优化器可能放弃索引走全表扫描——这时统计本身很快,但要注意是否真需要实时精确值,还是可接受近似计数(如 INFORMATION_SCHEMA.TABLES 中的 TABLE_ROWS)。










