1. 核心概念
COUNT 是 SQL 中常用的聚合函数,用于统计结果集的行数。但在不同的写法下,其统计规则和底层执行效率有所不同。
- COUNT(列名):统计该列中 非 NULL 值的行数。
- COUNT(*):统计 所有行,包括 NULL 值。
- COUNT(1):统计 所有行,包括 NULL 值(效果等同于
COUNT(*))。
2. 原理与区别
2.1 执行结果差异
最核心的区别在于对 NULL 值的处理:
-- 假设表中有 10 行数据,其中 column_a 有 2 行是 NULL
SELECT COUNT(column_a) FROM table; -- 结果:8
SELECT COUNT(*) FROM table; -- 结果:10
SELECT COUNT(1) FROM table; -- 结果:10
2.2 底层执行原理 (InnoDB)
COUNT(列名) InnoDB 会遍历整张表(或辅助索引),取出每一行的该列值,判断是否为 NULL。如果不为 NULL,则计数加 1。
- 性能:如果该列没有索引,需要回表或全表扫描,性能较差。
COUNT(*) MySQL 对 COUNT(*) 做了专门优化。它并不会把所有列取出来,而是专门找一棵 最小的二级索引树(Secondary Index)进行遍历统计。因为二级索引通常比聚簇索引(主键索引)小很多,所以 IO 开销更小。
- 注意:InnoDB 不像 MyISAM 那样存储了总行数,因为 MVCC 的存在,不同事务看到的行数可能不同,所以必须实时计数。
COUNT(1) 在 InnoDB 中,COUNT(1) 和 COUNT(*) 的执行计划 完全一样。MySQL 优化器会将 COUNT(1) 转化为 COUNT(*) 处理。
3. 性能对比与优化
结论:COUNT(*) ≈ COUNT(1) > COUNT(列名)
- COUNT(*) vs COUNT(1):
- 理论上
COUNT(*)是 SQL 标准语法,MySQL 优化器对其进行了特殊优化。 COUNT(1)只是一个常量表达式。- 在现代 MySQL 版本中,两者性能 没有区别。建议优先使用
COUNT(*)。
- 理论上
- COUNT(列名):
- 只有在必须排除 NULL 值时才使用。
- 否则,由于需要提取列值并判断 NULL,性能通常略低于
COUNT(*)。
- 优化建议:
- 如果需要频繁统计大表总行数,不要直接
SELECT COUNT(*)。 - 方案:自己维护一个计数表,或者使用 Redis 缓存(允许一定误差),或者参考
SHOW TABLE STATUS的估算值。
- 如果需要频繁统计大表总行数,不要直接
4. 总结
| 方式 | 统计范围 | NULL 值 | 性能 (InnoDB) | 推荐度 |
|---|---|---|---|---|
| COUNT(列名) | 指定列 | 忽略 | 较慢 (需判空) | 仅在需排除 NULL 时用 |
| COUNT(*) | 所有行 | 包含 | 最快 (走最小索引) | ⭐⭐⭐⭐⭐ |
| COUNT(1) | 所有行 | 包含 | 最快 (同上) | ⭐⭐⭐⭐ |
面试回答示例: “在 InnoDB 引擎中,COUNT(*) 和 COUNT(1) 性能基本一致,都会利用最小的二级索引进行扫描计数,且包含 NULL 值。而 COUNT(列名) 会忽略 NULL 值,且需要取出列值判断,性能稍差。 除非业务明确要求统计非 NULL 值,否则我一律推荐使用 COUNT(*),这是 SQL 标准且经过优化的写法。”