count()统计所有行,count(字段)仅统计该字段非null的行,二者语义不同且性能有差异:count()更高效、语义明确,而count(字段)易因null值导致结果偏少且性能较差。

查总数却用COUNT(字段),结果天然就少
因为 COUNT(字段) 只统计该字段值非 NULL 的行,而 COUNT(*) 统计所有满足条件的行——哪怕整行除了主键全是 NULL,它也算 1 行。这不是 bug,是 SQL 标准强制行为。
常见误用场景:
- 统计用户总数却写了
COUNT(phone),而部分用户没填手机号 → 结果比真实用户数少 -
LEFT JOIN后对右表字段做COUNT(orders.id),匹配失败时该字段为NULL,这一行直接被跳过 - 字段原本
NOT NULL,后来 DDL 改成允许NULL,查询结果静默变小,无任何报错提示
验证某字段有多少 NULL 值,别靠猜
直接对比两个数字最可靠:
SELECT COUNT(*) AS total, COUNT(email) AS email_non_null, COUNT(*) - COUNT(email) AS email_null_count FROM users;
如果 email_null_count > 0,说明该列确实存在 NULL;此时用 COUNT(email) 就必然漏掉这些行。很多线上问题的根源,就是开发查了 COUNT(*) 看总用户数,又用 COUNT(email) 算“有邮箱用户数”,却没意识到两者本就不该相等。
COUNT(*) 和 COUNT(1) 性能几乎一样,但语义更稳
COUNT(*) 被 MySQL 优化器深度适配:它知道你要的是“行数”,会主动选最小索引(比如只存主键的二级索引)遍历,不取任何字段值;COUNT(1) 虽然也走类似路径,但 server 层仍需对每行硬塞一个常量再判空——逻辑上恒真,但多了一层抽象。
关键差异点:
-
COUNT(*)在老版本或未ANALYZE的表上,优化器更敢放手选最优索引 -
COUNT(1)在极少数边界 case(如某些视图或子查询嵌套)中可能触发非预期执行计划 - 二者都不读数据行,I/O 开销基本一致;但
COUNT(*)的语义更直白,团队协作时不易误解
COUNT(字段) 最容易被忽略的性能陷阱
你以为加个索引就能救?不一定。即使字段有单列索引,COUNT(字段) 仍要逐行取值、判空,server 层无法跳过这步。而 COUNT(*) 连字段名都不需要,引擎可直接跳转到索引叶子节点扫一遍。
真正拖慢它的三个因素:
- 字段没索引 → 强制回聚簇索引,读整行只为取一个字段
- 字段类型大(如
TEXT或长VARCHAR)→ 即使有索引,也可能因不覆盖而回表 - 字段允许
NULL→ 每行多一次空值判断,无法向量化跳过
除非业务明确要“该字段非空的数量”,否则别用 COUNT(字段) 统计总行数。它既慢,又容易错——尤其是当你后来给该字段加了允许 NULL 的约束时,查询结果就静默变了。











