Skip to content
MySQL 变更与运维治理 · 第 1 篇 / 共 4 篇
领域数据与中间件
专题MySQL 专题
当前序列MySQL 变更与运维治理
阅读位置第 1 篇 / 共 4 篇当前专题第 4 个序列 / 共 7 个序列

COUNT(*) 和 COUNT(列) 到底差在哪

先说结论

  • COUNT(*) 统计的是行数
  • COUNT(列) 统计的是该列非 NULL 的行数
  • 真正的区别首先是语义,不是性能玄学

一、最核心的区别就是 NULL

看下面这个表意就够了:

  • COUNT(*):这一行在不在
  • COUNT(name):这一行的 name 有没有值

所以如果某列允许 NULL,两者结果天然可能不同。

这也是为什么很多统计 SQL 一上来就写 COUNT(id),但其实如果 id 本来就非空,它和 COUNT(*) 在结果上没有区别。

二、为什么很多人总拿性能说事

因为早些年不同数据库、不同版本、不同执行器实现下,确实会有一些细节差异。
但在 MySQL 里,日常业务真正更该先关心的是:

  • 你的统计语义对不对
  • 有没有 where 条件
  • 有没有合适索引
  • 是不是在扫太多数据

大多数慢统计问题,根因都不在“你用了 COUNT(*) 还是 COUNT(id)”。

三、什么时候应该优先写 COUNT(*)

当你就是想统计“满足条件的总行数”时,优先写 COUNT(*) 最清晰。

比如:

  • 统计订单总数
  • 统计当天注册用户数
  • 统计某状态下的记录数

它表达最直接,也最不容易误导后面维护的人。

四、什么时候写 COUNT(列) 才有明确意义

当你就是想统计“这个字段有值的记录数”时,COUNT(列) 才更合适。

比如:

  • 统计已填写手机号的用户数
  • 统计有发货时间的订单数
  • 统计有退款原因的记录数

这时候它表达的是业务语义,而不是性能技巧。

五、最容易踩的坑

1. 把 COUNT(id) 当成性能优化模板

如果 id 本来就非空,很多时候它只是换了种写法,并没有解决真正的统计成本问题。

2. 列允许 NULL 却误以为和 COUNT(*) 等价

这会直接导致统计结果偏差。

3. 统计慢了就只改 COUNT 写法

更应该先看:

  • 过滤条件
  • 索引命中
  • 扫描范围
  • 是否可以走汇总表或缓存

总结

COUNT(*)COUNT(列) 首先是语义差异,不是优化口诀。想统计总行数就用 COUNT(*),想统计某列非空值就用 COUNT(列)。真正的性能问题,多半还是要回到条件、索引和扫描范围上解决。

延伸阅读相关文章优先当前专题,再补跨专题关联。
同一序列 · 顺着当前主线继续读binlog、redo log、undo log 是怎么配合的适合把计数、日志、在线变更和归档清理放在一起看。MySQL 专题 · MySQL 变更与运维治理同一序列 · 顺着当前主线继续读大表在线 DDL 怎么做更稳适合把计数、日志、在线变更和归档清理放在一起看。MySQL 专题 · MySQL 变更与运维治理同专题其他序列 · MySQL 变更治理与数据生命周期大事务为什么会拖垮数据库适合把大事务、DDL、回填、慢 SQL 治理和归档分层放进一条持续治理主线上看。MySQL 专题 · MySQL 变更治理与数据生命周期同专题其他序列 · MySQL 事务、锁与高可用边界分库分表前最容易忽略哪些边界适合把 next-key lock、一致性读、复制延迟和分库分表边界放回同一条一致性主线上判断。MySQL 专题 · MySQL 事务、锁与高可用边界跨专题关联 · 同场景:基础学习@Conditional 系列注解怎么配合使用适合把自动装配、条件装配、Profile 和循环依赖放回 Spring Boot 启动过程里理解。Spring 专题 · Spring Boot 启动、装配与配置跨专题关联 · 同场景:基础学习保留策略和日志压缩适合哪些场景适合把副本机制、acks、批量压缩和日志组织放在一条 Kafka 投递主线上理解。Kafka 专题 · Kafka 投递链路与日志存储机制
继续阅读MySQL 变更与运维治理当前序列第 1 篇 / 共 4 篇当前专题第 4 个序列 / 共 7 个序列
往前看
上一序列MySQL 存储引擎与执行细节从第 1 篇开始:MVCC 和 Read View 怎么理解
往后看
下一篇binlog、redo log、undo log 是怎么配合的继续当前序列下一章下一序列MySQL 建模与查询执行路径从第 1 篇开始:宽表、范式和反范式怎么取舍

把零散经验整理成可查、可复用、可持续更新的企业级知识门户