这是一个非常经典的MySQL面试题。简单直接的答案是:在绝大多数情况下,使用 COUNT(*)。
下面我来详细解释这几种写法的区别和最佳实践,让你彻底明白。
结论先行
| 写法 | 含义 | 推荐度 | 原因 |
|---|---|---|---|
COUNT(*) | 统计所有行数,不管列值是否为NULL。 | ⭐⭐⭐⭐⭐ 强烈推荐 | 语义最清晰,性能最优(MySQL已经对其做了大量优化)。 |
COUNT(1) | 统计所有行数。1 这个常量值不为NULL,所以每一行都计数。 | ⭐⭐⭐⭐ 可以使用 | 性能与 COUNT(*) 完全一样,但语义不如 COUNT(*) 直观。 |
COUNT(0) | 同 COUNT(1),0 也是一个常量值。 | ⭐⭐⭐ 不推荐 | 性能一样,但容易让人困惑“为什么要统计0的个数?”,语义不清。 |
COUNT(column_name) | 统计指定列中不为NULL的行数。 | ⭐⭐⭐ 按需使用 | 功能不同!如果该列允许为NULL,结果可能小于总行数。 |
详细分解
1. COUNT(*)
这是 统计表总行数 的标准写法。它的目的非常明确:“给我所有行的数量”。
- 现代MySQL版本(5.7及以后)对
COUNT(*)做了大量优化,特别是对于InnoDB引擎。它会选择最小的非NULL二级索引来统计行数,如果没有二级索引,则使用主键索引。它不会去读取每一行的实际数据,因此效率很高。 - 语义清晰:一看就知道是要计算总记录数。
2. COUNT(1) 和 COUNT(0)
COUNT(1):对于每一行,引擎都会检查括号内的表达式是否为NULL。1是一个非NULL的常量表达式,因此每一行都会被计数。它的执行过程与COUNT(*)完全相同,性能没有任何区别。COUNT(0):原理同COUNT(1),0也是一个非NULL常量。但从代码可读性来看,它不如COUNT(1)和COUNT(*),因为0看起来像是一个具体的值,容易产生歧义。
性能真相:在MySQL中,
COUNT(*)、COUNT(1)、COUNT(0)、甚至COUNT('any_constant_value')的执行计划(Execution Plan)是完全一样的,性能没有任何差异。你可以用EXPLAIN语句来验证这一点。
3. COUNT(column_name)
这是与前三种完全不同的操作!
- 它不是统计总行数,而是统计指定列
column_name中值不为NULL的行的数量。 - 如果该列定义了
NOT NULL,那么COUNT(column_name)的结果会等于COUNT(*)。 - 如果该列允许为
NULL,并且表中确实存在NULL值,那么COUNT(column_name)的结果会小于COUNT(*)。
示例:
假设有一张 users 表,有5条记录,其中 phone 列有1条记录为NULL。
| id | name | phone |
|---|---|---|
| 1 | Alice | 12345 |
| 2 | Bob | 67890 |
| 3 | Charlie | NULL |
| 4 | David | 11111 |
| 5 | Eve | 22222 |
sql
SELECT
COUNT(*), -- 结果为 5 (总行数)
COUNT(1), -- 结果为 5
COUNT(0), -- 结果为 5
COUNT(phone) -- 结果为 4 (因为有一行的phone是NULL,不被计数)
FROM users;
最终建议与最佳实践
-
当你需要【统计表的总行数】时,永远使用
COUNT(*)。- 理由:这是SQL标准写法,语义最明确,所有人都能一眼看懂。并且MySQL已经对其进行了最优化的处理,性能最好。
-
当你需要【统计某列中非NULL值的数量】时,使用
COUNT(column_name)。- 理由:这是实现该需求的正确语法,例如“统计有多少个用户提供了手机号”。
-
避免使用
COUNT(0),因为它语义不清,不能带来任何好处。 -
可以接受
COUNT(1),但首选仍然是COUNT(*)。- 在一些古老的数据库系统中(或非常旧的MySQL版本),传说
COUNT(*)效率低,所以很多人养成了写COUNT(1)的习惯。但这个说法对于现代MySQL已经完全过时了。
- 在一些古老的数据库系统中(或非常旧的MySQL版本),传说
总结一句话:数总行数用 COUNT(*),数非空值用 COUNT(列名),其他写法没必要