count(*)统计行数,count(字段)仅统计该字段非null值;前者反映结果集行数,后者反映当前结果集中该字段非空数量,语义随where/group by/join变化而漂移。

COUNT(*) 统计的是行,COUNT(字段) 统计的是非NULL值
这是最根本的区别,不是bug,是SQL标准强制语义。COUNT(*)数的是结果集里有多少行,哪怕所有字段都是NULL,这行也计入;而COUNT(email)只看email这一列——只要该列值为NULL,整行就被跳过。
常见现象:COUNT(*)返回100,COUNT(email)返回92,说明有8行email是NULL。注意:''(空字符串)、0、false全算非NULL,只有数据库层面真正的NULL被排除。
- 差值直接等于该列
NULL数量,比查表结构或翻文档更快更准 -
WHERE和GROUP BY之后才做NULL判断,所以COUNT(字段)反映的是当前结果集里的分布,不是原始表
LEFT JOIN后用COUNT(字段)极易误判主表基数
在LEFT JOIN users u ON u.id = o.user_id这类查询中,对右表字段如o.order_id用COUNT(o.order_id),统计的是“成功关联的订单行数”,不是“用户数”。1个用户有3个订单,JOIN后变成3行,COUNT(o.order_id)就返回3。
如果右表外键字段本身允许NULL(比如没加NOT NULL约束),COUNT(o.user_id)还会剔除user_id IS NULL的行——结果既不是用户数,也不是订单总数,语义完全错位。
- 想统计左表原始行数,应写
COUNT(DISTINCT u.id) - 想统计右表匹配行数,且右表字段已定义
NOT NULL,COUNT(o.id)才安全 - 用
COUNT(*)配合GROUP BY u.id可清晰区分每组行数
COUNT(*)性能通常优于COUNT(字段),尤其字段无索引或NULL比例高时
COUNT(*)只需确认行存在性,优化器常走最小索引(如主键)甚至元数据估算;COUNT(email)必须读取email列值并逐行判空,尤其当该列允许NULL且无索引时,大概率触发全表扫描。
这些隐式开销比函数写法本身影响更大:
- 字段未加
NOT NULL约束 - 该列没有索引
- 表中
NULL占比高(比如日志表里error_code只在出错时有值) - 即使
id是主键,只要它允许NULL,COUNT(id)仍要判空,无法完全跳过回表
COUNT(*)和COUNT(1)在主流引擎中性能几乎一致,别为“看起来快”改写
MySQL 8.0+、PostgreSQL、SQL Server等都会将COUNT(*)和COUNT(1)优化为相同执行计划。MySQL官方文档明确说二者处理方式完全相同。
MyISAM表对COUNT(*)有特殊缓存优化(直接读表头行数),而COUNT(1)不一定享受同等待遇;InnoDB虽无缓存,但从8.0.13起也对无条件COUNT(*)做了扫表路径优化。
- 优先用
COUNT(*):它是SQL92标准语法,语义最清晰,且现代优化器最熟悉它 -
COUNT(1)不是错误,但没必要刻意替换COUNT(*) - 阿里开发手册【强制】要求:不要用
COUNT(列名)或COUNT(常量)替代COUNT(*)
COUNT(字段),统计的从来不是“这张表里这个字段有多少非空”,而是“当前SELECT结果集里这个字段有多少非空”——中间可能经过了JOIN、WHERE、GROUP BY,语义已经漂移。











