COUNT(*)统计结果集所有行,COUNT(列名)仅统计该列非NULL值;二者差值即该列NULL数量,这是SQL标准强制行为,非bug。

COUNT(*) 和 COUNT(列名) 返回不同结果,不是 bug,也不是数据库出错,而是 SQL 标准强制规定的行为:前者数行,后者数非 NULL 值。
为什么 COUNT(列名) 总是 ≤ COUNT(*)
COUNT(列名) 在执行时会对结果集里的每一行检查该列是否为 NULL,只有非 NULL 才计入;而 COUNT(*) 完全不读任何列,只要这一行在当前结果集中存在,就加 1。
-
''(空字符串)、0、false都算非NULL,会被COUNT(列名)计入 - 真正的
NULL(不是字符串'NULL')才会被跳过 - 差值
COUNT(*) - COUNT(列名)就是该列在当前结果集中的NULL行数,比查表结构或猜业务更可靠
LEFT JOIN 后 COUNT(右表.列名) 为什么“少得离谱”
LEFT JOIN 中右表无匹配时,对应字段值为 NULL,COUNT(右表.列名) 会把这些行全部剔除,实际统计的是“成功关联的行数”,不是左表原始行数,也不是右表总行数。
-
COUNT(*)统计的是连接后物理行数 → 一对多时会膨胀(1 用户 × 3 订单 = 3 行) -
COUNT(users.id)只统计users.id IS NOT NULL的行 → 漏掉没关联用户的订单 - 想统计左表原始行数,必须用
COUNT(DISTINCT left_table.id)或子查询
COUNT(*) 为什么通常比 COUNT(列名) 更快
COUNT(*) 只需确认行存在性,优化器可走最小索引(如主键)甚至元数据估算;COUNT(列名) 必须读取该列值并逐行判空,尤其当该列允许 NULL 且无索引时,大概率触发全表扫描。
- 即使该列有索引,若定义为允许
NULL,优化器仍需回表确认每行真实值,无法跳过判空逻辑 -
COUNT(id)即使id是主键,只要没加NOT NULL约束,就不能免于判空 -
COUNT(*)和COUNT(1)在 MySQL 8.0+、PostgreSQL 等主流引擎中执行计划几乎一致,无需刻意替换
最容易被忽略的 NULL 处理时机
NULL 判断发生在 WHERE、GROUP BY、JOIN 之后——COUNT(列名) 统计的是当前中间结果集里该列的非空数量,不是原始表分布。
比如 SELECT COUNT(email) FROM users WHERE status = 'active',统计的是“活跃用户中填了邮箱的人数”,不是“全部用户中填邮箱的人数”。这种偏差不会报错,只会静默影响指标口径。

















