COUNT(*)统计所有行(含NULL),COUNT(字段名)仅统计该字段非NULL的行;二者差异源于SQL标准,非数据库Bug。

为什么 COUNT(字段名) 和 COUNT(*) 返回的数值经常不一样
因为 COUNT(*) 统计的是所有行(包括含 NULL 的行),而 COUNT(字段名) 只统计该字段**非 NULL 的行**。这是 SQL 标准行为,不是数据库 Bug。
常见误解是以为两者都数“有数据的行”,但其实 COUNT(字段名) 本质是“过滤掉 NULL 后再计数”。如果某列大量为 NULL(比如可选填写的 phone 字段),COUNT(phone) 就会明显小于 COUNT(*)。
COUNT(字段名) 真正适合统计“非空记录”的场景
当你明确想统计某个业务字段实际有值的行数时,用 COUNT(字段名) 最直接。比如:
- 统计已留手机号的用户数 →
COUNT(phone) - 统计完成实名认证的订单数 →
COUNT(id_card_no) - 统计有备注的工单数量 →
COUNT(note)
注意:这里“非空”仅指非 NULL,不自动排除空字符串 '' 或空白字符串 ' '。如果业务上这些也算“无值”,得额外加条件:COUNT(CASE WHEN trim(note) != '' THEN note END)。
别在 WHERE 里写 IS NOT NULL 还套 COUNT(*)
有人写 SELECT COUNT(*) FROM t WHERE status IS NOT NULL 来统计非空,逻辑没错,但多了一次扫描过滤;而 SELECT COUNT(status) 在语义和执行效率上通常更简洁——多数引擎对 COUNT(字段) 会直接跳过 NULL 值,无需额外谓词。
但要注意例外:
- MySQL 5.7+ 对
COUNT(字段)在某些索引覆盖场景下可能不如COUNT(*)快(尤其字段无索引且表极大) - PostgreSQL 中两者计划几乎一致,优先选语义清晰的
COUNT(字段) - 如果字段有允许
NULL的唯一索引,COUNT(字段)实际等于该索引中非空键的数量
COUNT(1)、COUNT(任意常量) 和 COUNT(*) 完全等价
COUNT(1)、COUNT('x')、COUNT(42) 都等同于 COUNT(*),它们都统计所有行,无视任何值内容。不要误以为 COUNT(1) 比 COUNT(*) 更快或更“明确”——现代优化器全会转成同一执行路径。
真正影响结果的,只有你传进去的是“*”还是具体字段名。写 COUNT(1) 图省事可以,但想统计非空值,必须换字段名,否则没意义。
容易被忽略的一点:如果目标字段是表达式(如 COUNT(email LIKE '%@%')),它依然只算非 NULL 结果——而布尔表达式在 SQL 中 TRUE/FALSE 都是非 NULL,所以这种写法会返回总行数,达不到“统计邮箱格式有效行”的目的。真要那样,得用 SUM(CASE WHEN email LIKE '%@%' THEN 1 ELSE 0 END)。

















