一、三种写法分别是什么意思
1. COUNT(*)
SELECT COUNT(*)
FROM users;
表示:
统计结果集中的行数,不管这一行的字段是否为 NULL。
这是最标准、最清晰的“统计行数”写法。
2. COUNT(1)
SELECT COUNT(1)
FROM users;
1 是一个永远不为 NULL 的常量。
对于结果集中的每一行,MySQL 都可以得到一个非 NULL 的 1,所以最终统计的仍然是行数:
每行 -> 计算出 1 -> 计数加一
因此,对 InnoDB 来说:
COUNT(*)
COUNT(1)
MySQL 官方明确说明,这两种写法处理方式相同,没有性能差异。
3. COUNT(主键字段)
SELECT COUNT(id)
FROM users;
它统计的是:
id 字段不为 NULL 的行数。
如果 id 是真正的主键,那么主键必然是 NOT NULL,因此:
COUNT(*)
COUNT(1)
COUNT(id)
通常得到相同结果。
二、举个例子
表中有三行:
| 1 | 张三 | 正常 |
| 2 | 李四 | NULL |
| 3 | 王五 | 正常 |
执行:
SELECT
COUNT(*) AS c1,
COUNT(1) AS c2,
COUNT(id) AS c3,
COUNT(remark) AS c4
FROM users;
结果是:
c1 = 3
c2 = 3
c3 = 3
c4 = 2
原因:
COUNT(*):统计所有行
COUNT(1):1 永不为 NULL,统计所有行
COUNT(id):id 是主键,全部非 NULL
COUNT(remark):忽略 remark IS NULL 的那一行
MySQL 中,COUNT(expr)统计的是表达式不为 NULL 的数量,而 COUNT(*)统计行数。
三、哪个性能最好?
对 InnoDB 来说
通常结论是:
COUNT(*) = COUNT(1)
性能基本相同。
如果主键字段是:
id BIGINT PRIMARY KEY
那么:
COUNT(id)
结果通常也相同,但没有必要为了性能特意使用它。
实际开发中推荐:
SELECT COUNT(*)
FROM users;
原因是:
语义最清晰
表达的是“统计行数”
MySQL 对它有明确的优化路径
不会因为字段是否允许 NULL 而产生误差
MySQL 官方说明,InnoDB 会按照当前事务可见性统计记录,并且对于没有额外 WHERE、GROUP BY 的 COUNT(*),通常会遍历可用的最小二级索引;没有二级索引时才扫描聚簇索引。
所以实际性能主要取决于:
是否有合适的 WHERE 条件
是否有合适的索引
索引大小
数据是否已经在 Buffer Pool 中
当前事务可见的数据量
表中数据规模
而不是取决于 *、1 还是主键字段。
四、一个容易出错的场景:LEFT JOIN
考虑:
SELECT
u.id,
COUNT(*),
COUNT(o.id)
FROM users u
LEFT JOIN orders o
ON u.id = o.user_id
GROUP BY u.id;
假设用户张三没有订单。
LEFT JOIN 仍然会保留张三这一行,只是订单表字段全部为 NULL:
u.id o.id
1 NULL
因此:
COUNT(*) = 1
COUNT(o.id) = 0
这里两者含义不同:
COUNT(*):统计连接结果中的行数
COUNT(o.id):统计真正匹配到订单的数量
因此统计子表数量时,经常应该写:
COUNT(o.id)
而不是:
COUNT(*)
五、MyISAM 的特殊情况
如果使用 MyISAM,且查询是:
SELECT COUNT(*)
FROM users;
没有 WHERE 条件时,MyISAM 可以利用存储的行数信息快速返回结果。
但 InnoDB 不保存一个可以直接返回的精确总行数,因为不同事务可能看到不同的数据版本。
不过现在绝大多数业务表使用 InnoDB,所以通常记住 InnoDB 的结论即可。


