count()统计结果集的行数,不忽略null;count(列名)统计该列非null值数量,结果≤count();二者性能差异源于null检查及索引利用,left join后需警惕语义混淆。

count(*) 统计的是行,不是列值
它不看任何列的内容,只回答“这张结果里一共有多少行”。哪怕所有列都是 NULL,只要这一行存在,COUNT(*) 就会把它算进去。这是最接近“物理行数”的统计方式。
常见错误现象:
写 SELECT COUNT(*) FROM users WHERE status = 'active' 却发现结果比预期少——其实不是函数错了,是 WHERE 已经过滤掉非 active 行,COUNT(*) 只是在这个过滤后的结果集上数行数,行为完全正确。
count(列名) 统计的是该列的非NULL值数量
它逐行检查指定列:值为 NULL 就跳过,不为 NULL(哪怕是空字符串 ''、数字 0、布尔 false)就算一个。所以结果永远 ≤ COUNT(*),差多少,就说明那列有多少个 NULL。
使用场景举例:
- 查“有多少用户填了手机号” →
COUNT(phone) - 查“有多少订单有实际支付金额” →
COUNT(paid_amount) - 但绝不能用
COUNT(email)代替COUNT(*)来估算总用户数,除非你确定 email 列无 NULL
性能差异主要来自 NULL 检查和索引利用
COUNT(*) 在现代数据库(如 MySQL InnoDB 8.0+、PostgreSQL、SQL Server)中通常能走主键或元数据优化,尤其在无 WHERE 时可能免于全表扫描。
COUNT(列名) 必须检查每行该列是否为 NULL,如果该列没索引,引擎大概率要读取整列数据;即使有索引,也得确认索引项对应行是否真实存在(避免幻读等),开销略高。
参数差异提醒:
-
COUNT(id)(id 是主键)≈COUNT(*)性能,因为主键不可能为NULL,且索引覆盖好 -
COUNT(created_at)(有索引但允许 NULL)→ 引擎可能用索引,但仍需判空 -
COUNT(note)(无索引、常为 NULL)→ 很可能触发全表扫描,慎用
LEFT JOIN 后 count(*) 和 count(右表列名) 容易混淆
这是最常被忽略的坑。比如 SELECT COUNT(*) FROM orders LEFT JOIN users ON orders.user_id = users.id,如果一个 order 对应多个 users(理论上不该,但若设计异常或测试数据如此),COUNT(*) 会把重复连接的行全算上;而 COUNT(users.id) 虽然也受连接影响,但会把右表为 NULL 的那些行排除——两者语义完全不同。
真正想问“有多少订单”,应该写:SELECT COUNT(DISTINCT orders.id) FROM orders LEFT JOIN users ...
或者更稳妥地,先聚合再连接,而不是依赖 COUNT 的位置。











