COUNT(*)、COUNT(1) 和 COUNT(列) 有什么区别?
简化版
COUNT(*) 统计结果集的行数,COUNT(1) 统计常量 1 的非空次数,因此结果通常与 COUNT(*) 相同;COUNT(column) 只统计该列非 NULL 的行。以 MySQL InnoDB 为例,官方明确说明 COUNT(*) 与 COUNT(1) 的处理方式相同,不存在固定的性能高低。
详细版
假设表中有 5 行,其中 email 有 2 行为 NULL:
SELECT COUNT(*), COUNT(1), COUNT(email), COUNT(DISTINCT email)
FROM users;
结果含义分别是:总行数 5、常量非空次数 5、非空邮箱数 3、去重后的非空邮箱数。COUNT(NULL) 的结果为 0,因为表达式在每一行都是 NULL。
选择写法应先看业务语义:统计行数用 COUNT(*) 最清楚,统计某字段已填写的记录数用 COUNT(column),统计不同非空值用 COUNT(DISTINCT column)。
完整版教学
一、COUNT 统计的是非 NULL 输入
COUNT(expr) 对每一行计算表达式,只累计结果不为 NULL 的次数:
COUNT(1) -- 1 始终非 NULL,统计每一行
COUNT(email) -- email 为 NULL 的行不计入
COUNT(NULL) -- 每行都是 NULL,结果为 0
COUNT(*) 是专门统计行数的语法,不要求某一列非空,也不会因为某些列为 NULL 而漏计。
二、为什么 COUNT(*) 比 COUNT(列) 更适合统计总行数
即使某列当前被定义为 NOT NULL,COUNT(column) 仍表达“统计此表达式非空的数量”,而 COUNT(*) 直接表达“统计行数”。后续字段约束变化、外连接产生补齐行时,两者的语义差异尤其重要。
例如:
SELECT d.id, COUNT(e.id) AS employee_count
FROM department AS d
LEFT JOIN employee AS e ON e.department_id = d.id
GROUP BY d.id;
空部门的连接结果仍有一行,但 e.id 为 NULL,所以 COUNT(e.id) 得到 0。若写 COUNT(*),空部门会被计为 1。
三、COUNT(1) 会不会更快
不能给出跨数据库、跨版本的绝对结论。现代优化器知道常量 1 永不为空,通常能把它与行数统计等价处理。MySQL 8.4 的 InnoDB 文档明确说明 COUNT(*) 和 COUNT(1) 处理相同,没有性能差异。
性能真正取决于存储引擎、可见性规则、过滤条件、索引与执行计划。InnoDB 因为 MVCC 下不同事务可见的行数可能不同,不为整张表长期保存一个对所有事务都准确的总行数;无条件 COUNT(*) 仍需统计当前事务可见的记录。
四、COUNT(DISTINCT) 的注意点
COUNT(DISTINCT email)
它统计不同的非 NULL 值,重复邮箱只算一次,NULL 不计入。多列 COUNT(DISTINCT ...) 的具体语法和空值处理存在数据库差异,写跨数据库 SQL 时需要查目标产品文档。
五、常见误区
- 以为
COUNT(*)会读取整行全部列;它表达统计行数,优化器会选择合适的访问方式; - 以为
COUNT(1)永远更快;这忽略了优化器和存储引擎; - 在外连接中用
COUNT(*)统计右表匹配数,导致无匹配时得到 1; - 把
COUNT(column)当成总行数,却忘记该列允许NULL。
六、常见误区与追问
| 写法 | 统计对象 | 典型结果差异 |
|---|---|---|
COUNT(*) | 当前结果集行数 | 外连接无匹配补空行也算 1 行 |
COUNT(1) | 常量 1 的非空次数 | 通常与 COUNT(*) 相同 |
COUNT(column) | 指定列非 NULL 次数 | 列为 NULL 的行不计入 |
COUNT(DISTINCT column) | 不同的非 NULL 值 | 重复值只算 1 次 |
- 误区:COUNT(*) 会把整行所有列都读出来。
COUNT(*)的语义是统计行数,优化器会选择合适的访问路径;它不等于把每一列都取出来再数。 - 误区:COUNT(1) 一定比 COUNT(*) 快。 现代数据库通常能把常量计数和行数计数等价优化,MySQL InnoDB 也明确把二者按相同方式处理,面试中不要背性能口号。
- 误区:COUNT(column) 可以无条件代表总行数。 只要该列允许
NULL,它统计的就是非空值数量;在外连接补空行时,这个差异尤其明显。 - 追问:LEFT JOIN 后统计右表匹配数应该用哪个 COUNT? 通常用
COUNT(右表非空主键或关联键),因为无匹配时右表列为NULL,结果才会是 0。 - 追问:COUNT(DISTINCT column) 会统计 NULL 吗? 通常只统计不同的非
NULL值,多个NULL不会贡献计数;多列DISTINCT的语法和空值细节要看具体数据库。 - 追问:为什么 InnoDB 不直接保存精确总行数? MVCC 下不同事务可见的行版本不同,同一张表在两个隔离视图里“总行数”可能不同,所以精确
COUNT(*)需要按当前可见性判断。
记忆钩子:先问“数行还是数值”,再问“值会不会 NULL”,最后才讨论具体数据库的执行计划。
七、加强记忆
统计行就用 COUNT(),统计某列已填值就用 COUNT(列),去重非空值用 COUNT(DISTINCT 列)。COUNT(1) 与 COUNT() 通常结果相同,MySQL InnoDB 对二者同样处理;不要背性能口诀,应看语义、数据库实现和执行计划。