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(列名)

  1. COUNT(*) vs COUNT(1)
    • 理论上 COUNT(*) 是 SQL 标准语法,MySQL 优化器对其进行了特殊优化。
    • COUNT(1) 只是一个常量表达式。
    • 在现代 MySQL 版本中,两者性能 没有区别。建议优先使用 COUNT(*)
  2. COUNT(列名)
    • 只有在必须排除 NULL 值时才使用。
    • 否则,由于需要提取列值并判断 NULL,性能通常略低于 COUNT(*)
  3. 优化建议
    • 如果需要频繁统计大表总行数,不要直接 SELECT COUNT(*)
    • 方案:自己维护一个计数表,或者使用 Redis 缓存(允许一定误差),或者参考 SHOW TABLE STATUS 的估算值。

4. 总结

方式 统计范围 NULL 值 性能 (InnoDB) 推荐度
COUNT(列名) 指定列 忽略 较慢 (需判空) 仅在需排除 NULL 时用
COUNT(*) 所有行 包含 最快 (走最小索引) ⭐⭐⭐⭐⭐
COUNT(1) 所有行 包含 最快 (同上) ⭐⭐⭐⭐

面试回答示例: “在 InnoDB 引擎中,COUNT(*)COUNT(1) 性能基本一致,都会利用最小的二级索引进行扫描计数,且包含 NULL 值。而 COUNT(列名) 会忽略 NULL 值,且需要取出列值判断,性能稍差。 除非业务明确要求统计非 NULL 值,否则我一律推荐使用 COUNT(*),这是 SQL 标准且经过优化的写法。”