count(列名)忽略null是sql标准强制行为,非bug;它仅统计该列is not null的行,而count(*)统计所有匹配行,差值即该列null数量。

COUNT(列名)忽略NULL是SQL标准定义的行为,不是bug,也不是数据库厂商的随意设计。它严格遵循三值逻辑(True/False/Unknown)中对NULL语义的处理:NULL表示“未知值”,既不等于任何值,也不能参与计数判定。所以当你写 COUNT(status),数据库只对 status IS NOT NULL 的行做计数——这是语义正确性要求,不是性能妥协。
为什么COUNT(*)和COUNT(列名)结果差很多
常见错误是误用 COUNT(列名) 替代总行数统计。比如订单表里 coupon_code 允许为空,执行 COUNT(coupon_code) 实际只算用了优惠券的订单,漏掉所有未用券的记录。
-
COUNT(*):统计所有匹配 WHERE 条件的行,不管字段是否为 NULL -
COUNT(列名):仅统计该列值IS NOT NULL的行 - 想查某列有多少 NULL?直接用
COUNT(*) - COUNT(列名),不用子查询或WHERE 列名 IS NULL再 COUNT
MySQL/PostgreSQL/SQL Server 都一致,但优化器表现不同
虽然语义统一,但底层执行差异明显:
- 无 WHERE 时,
COUNT(*)可能直接读取元数据(如 MyISAM)或最小索引(InnoDB),极快;COUNT(列名)必须逐行检查该列是否为 NULL,无法跳过数据页 - 即使该列定义为
NOT NULL,优化器也不一定自动等价转换为COUNT(*),执行计划仍可能走全列扫描 - 在有索引的列上用
COUNT(列名),若该列允许 NULL,数据库大概率无法利用索引的统计信息,退化为实际读取
别在WHERE里用COALESCE绕过NULL判断
有人想“统一处理空值”,写成 WHERE COALESCE(phone, '') != '',这会导致索引完全失效——因为函数包裹列后,数据库无法做索引查找(B+树比较失效)。真正要查非空手机号,就该写 WHERE phone IS NOT NULL AND phone != '';要查 NULL,就写 WHERE phone IS NULL。
真正容易被忽略的点在于:NULL 不是“空字符串”,也不是“零”,它是缺失值标识。一旦混用 COALESCE、IFNULL 或运算符(如 !=)去和 NULL 对比,逻辑就进入 Unknown 分支,结果不可预测。










