count(*)统计所有物理行数,不忽略null;count(列名)仅统计该列非null值的行数,故结果可能不同。

为什么 COUNT(*) 和 COUNT(列名) 返回的数字不一样
因为 COUNT(*) 统计的是结果集中的**物理行数**,不管任何列是否为 NULL;而 COUNT(列名) 只统计该列**值不为 NULL 的行**。这不是 bug,是 SQL 标准强制要求的行为。
-
COUNT(*):哪怕整行所有字段都是NULL,只要这一行存在,就算 1 -
COUNT(email):只要email IS NULL,这一行就跳过,不计入 - 常见误判:用
COUNT(phone)当“用户总数”,但部分用户没填手机号 → 结果偏小 - LEFT JOIN 后对右表字段(如
o.order_id)做COUNT(o.order_id),匹配失败时该字段为NULL,结果可能全为 0
怎么快速验证某列有多少 NULL 值
直接对比两个聚合结果最直观,不用查表结构或猜业务逻辑:
SELECT COUNT(*), COUNT(email), COUNT(*) - COUNT(email) AS null_count FROM users;
- 差值就是
email列中NULL的数量 - 如果差值大,说明该列空值率高,
COUNT(email)就不能代表“总记录数” - 注意:
''(空字符串)和'NULL'(字符串)不是NULL,COUNT(列名)仍会把它们算进去
想排除 NULL 和空字符串,该怎么写
COUNT(列名) 对空字符串无感,必须显式过滤:
SELECT COUNT(CASE WHEN status IS NOT NULL AND status != '' THEN 1 END) FROM orders;
- 别用
COUNT(NULLIF(status, ''))—— 在某些数据库(如 PostgreSQL)里它返回0,不是你想要的计数 - MySQL 支持
NULLIF,但嵌套后仍需配合COUNT()或SUM(),不如CASE直观可控 - 如果字段类型是 JSON 或 TEXT,还要考虑
TRIM(),否则带空格的' '也会被漏掉
COUNT(1) 和 COUNT(*) 真的有性能差别吗
在现代主流数据库(MySQL 8.0+、PostgreSQL、SQL Server、Oracle)中,COUNT(1) 和 COUNT(*) 的执行计划几乎完全一致,优化器会自动等价转换。所谓“COUNT(1) 更快”是过时机型或旧版本的遗留印象。
- MyISAM 引擎下
COUNT(*)可能走元数据缓存,但 InnoDB 默认不缓存行数 - 如果表有主键且是聚簇索引,
COUNT(主键)有时能走覆盖索引,但前提是该主键列允许NULL—— 实际上主键列不可能为NULL,所以它和COUNT(*)效果一样 - 真正影响性能的是是否能利用索引:比如
COUNT(created_at)在该字段有非空约束 + 索引时,可能比COUNT(*)快一点;但一旦该列允许NULL,优化器大概率放弃索引扫描
最常被忽略的一点:关联查询(尤其是 LEFT JOIN)之后再套 COUNT(列名),既受 NULL 影响,又受连接膨胀影响——这时候你得先明确,自己到底想数“连接后有多少行”,还是“左表有多少实体”,语义一乱,加多少括号都救不回来。











