# MySQL 中 count(*)、count(1) 和 count(字段名) 有什么区别?
7 min read
Table of Contents
MySQL 中 count(*)、count(1) 和 count(字段名) 有什么区别?
MySQL 中 count(*)、count(1) 和 count(字段名) 的区别
【核心定义】
COUNT() 是一个聚合函数,用于统计结果集的行数,但根据传入参数的不同,其统计逻辑和性能表现存在差异。
【关键要点】
COUNT(*):统计结果集的总行数,不关心具体列的值是否为 NULL。它是 SQL 标准中定义的统计行数的规范方式。COUNT(1):统计结果集的总行数,MySQL 会为每一行生成一个值为 1 的常量列进行统计,效果与COUNT(*)完全等价。COUNT(字段名):统计指定字段在结果集中 非 NULL 值的数量。如果该字段存在 NULL 值,则这些行不会被计入总数。
【深度推导/细节】
性能与执行计划分析(现代 MySQL 版本,如 5.7+、8.0)
COUNT(*)与COUNT(1)的优化:在 MySQL 中,优化器对这两者进行了相同的优化处理。当表没有WHERE条件时,如果存在一个可用的二级索引(通常选择最小的非主键索引),InnoDB 引擎会通过扫描该索引来统计行数,因为二级索引通常比聚簇索引(包含全部行数据)体积更小,I/O 更少。如果没有二级索引,则会扫描聚簇索引。两者性能无差异。COUNT(字段名)的额外开销:该操作需要:- 读取每一行的该字段值。
- 判断该值是否为 NULL。
- 只对非 NULL 值进行累加。 如果该字段上没有索引,则需要进行全表扫描(或索引扫描)来获取字段值。如果该字段上有索引,优化器可能会选择扫描这个索引来避免回表,但依然需要判断 NULL 值。
关于“统计行数”的最佳实践
- 明确统计意图:如果需要知道总共有多少行,使用
COUNT(*)。这是最清晰、语义最准确的写法。 - 避免使用
COUNT(字段名)来统计总行数,除非你明确需要排除该字段为 NULL 的行。 - 关于
COUNT(主键):COUNT(主键)在逻辑上等同于COUNT(*),因为主键非空。但在执行时,它需要读取每一行的主键值并进行非空判断,理论上比优化后的COUNT(*)开销略大(尽管在多数情况下差异微小)。
【关联/对比】
- 与
SUM()的对比:COUNT统计的是行数(计数),而SUM统计的是值的总和(累加)。 - 在事务隔离级别下的表现:在
REPEATABLE READ(默认级别)下,一个事务中的COUNT(*)会基于事务开始时的快照来提供一致性的行数视图。而MyISAM引擎(非事务性)会在没有WHERE条件时直接返回存储在元数据中的精确行数,速度极快,但此特性不适用于 InnoDB。
『面试官追问』
- 为什么说
COUNT(*)在 InnoDB 中慢?有什么优化方法?- 原因:InnoDB 支持事务和 MVCC,同一时刻不同事务看到的数据行数可能不同,因此无法像 MyISAM 一样在元数据中维护一个精确的计数器。
COUNT(*)需要扫描索引来计算。 - 优化:
- 使用近似值:通过
SHOW TABLE STATUS命令的Rows字段获取估算值。 - 使用计数表:单独维护一张表,在增删数据时通过事务同步更新一个计数器。
- 增加冗余的二级索引:为
COUNT(*)提供更小的扫描目标。
- 使用近似值:通过
- 原因:InnoDB 支持事务和 MVCC,同一时刻不同事务看到的数据行数可能不同,因此无法像 MyISAM 一样在元数据中维护一个精确的计数器。
COUNT(*)在 MySQL 8.0 中有什么改进吗?- MySQL 8.0 对 InnoDB 的并行扫描做了增强,对于大表的
COUNT(*)操作,在特定条件下可以利用多线程扫描,提升速度。但核心的计数逻辑未变。
- MySQL 8.0 对 InnoDB 的并行扫描做了增强,对于大表的
COUNT(1)中的 “1” 会被存储或计算吗?- 不会。“1” 是一个常量字面量。优化器在解析阶段就知道它不为 NULL,因此其执行路径与
COUNT(*)被优化后的路径一致,不会为每一行生成或存储这个值。
- 不会。“1” 是一个常量字面量。优化器在解析阶段就知道它不为 NULL,因此其执行路径与
【版本差异与总结】
- 历史误区:在非常古老的 MySQL 版本或某些数据库系统中,
COUNT(*)可能因为需要读取整行而较慢,COUNT(1)或COUNT(主键)曾被当作优化手段。在现代 MySQL(尤其是 5.7 及以后版本)中,这个性能差异已不存在。 - 最终结论:
- 语义优先:统计表行数,总是使用
COUNT(*)。这符合 SQL 标准,语义清晰,且在现代 MySQL 中性能最优。 COUNT(1):是COUNT(*)的一种等价写法,可读性稍逊,但结果和性能相同。COUNT(字段名):用于统计特定字段的非 NULL 值数量,目的不同,不可与前两者混用。
- 语义优先:统计表行数,总是使用
回答建议:在面试中,可以直接果断地给出结论:“在现在的 MySQL 里,COUNT(*) 和 COUNT(1) 性能是一样的,推荐用 COUNT(*) 来统计行数。而 COUNT(字段) 是统计非空值的,用途不一样。” 然后可以展开解释优化器的行为和 InnoDB 的实现机制,以体现深度。