count()统计所有行,count(列名)仅统计该列非null行;二者差值即该列null数量;join后count()统计连接后行数,需用count(distinct 列)统计主表去重数量。

为什么COUNT(*)和COUNT(列名)结果不同
COUNT(*)统计的是结果集里的物理行数,不管任何列是否为NULL;COUNT(列名)只统计该列值不为NULL的行。这不是bug,是SQL标准强制行为——只要该列有哪怕1个NULL,两者结果就必然不等。
COUNT(列名)少掉的那些行去哪了
差值就是该列NULL的数量。常见现象:COUNT(id)返回98,COUNT(*)返回100 → 说明有2行的id是NULL。注意:''(空字符串)、0、false都算非NULL,只有真正的NULL被跳过。
-
COUNT(*) - COUNT(列名)是最直接的NULL计数方式,比查表结构或猜业务逻辑更可靠 - LEFT JOIN后对右表字段(如
users.id)做COUNT(users.id),实际统计的是“成功关联的行数”,不是左表原始行数 - 即使主键字段没加
NOT NULL约束,也可能存NULL——别假设主键一定非空
COUNT(*)在JOIN后为什么不能乱用
COUNT(*)在JOIN后统计的是连接后的总行数,不是主表原始行数。一对多关联(比如1个用户对应3个订单)会让1行变3行,COUNT(*)就把这3行全算上。
- 想统计“有多少用户”,写
COUNT(DISTINCT users.id),而不是COUNT(*)或COUNT(orders.user_id) -
COUNT(orders.user_id)会把user_id IS NULL的行全剔除,结果既不是用户数,也不是订单数 - 真正要“有多少订单”,必须明确语义:是总数?已关联的?还是去重后的?不能靠函数位置推断
性能差异到底来自哪
COUNT(*)只需确认行存在性,优化器常走最小索引(如主键)甚至元数据估算;COUNT(列名)必须读取该列值并逐行判空,尤其当该列允许NULL且无索引时,大概率触发全表扫描。
-
COUNT(1)和COUNT(*)在MySQL 8.0+、PostgreSQL等主流引擎中执行计划几乎一致,别为“看起来快”刻意替换 - 即使
id是主键,只要它允许NULL,COUNT(id)仍要判空,无法完全跳过回表 -
COUNT(列名)的性能陷阱常藏在隐式依赖里:字段没加NOT NULL约束、没建索引、NULL比例高——这些比函数写法本身影响更大
最易被忽略的一点:NULL处理发生在WHERE和GROUP BY之后,COUNT(列名)反映的是当前结果集里该列的非空数量,不是原始表分布。一旦在复杂查询里混淆这点,指标偏差不会报错,只会静默出错。











