count(1) 和 count(*) 在 mysql 8.0+ 中性能完全一致,优化器将其等价折叠为行存在性计数,均不读取任何列值,仅依赖索引记录头判断行存在,i/o、cpu、缓冲池访问模式全相同。

MySQL 8.0+ 中 COUNT(1) 和 COUNT(*) 执行计划完全一致
执行 EXPLAIN SELECT COUNT(1) FROM orders 和 EXPLAIN SELECT COUNT(*) FROM orders,你会看到两者的 type、key、rows、Extra 字段完全相同——优化器在解析阶段就将两者等价折叠为「行存在性计数」操作,后续走的都是同一套索引扫描逻辑(比如扫主键 B+ 树叶子节点,或最小二级索引)。这不是“差不多快”,而是底层指令流、I/O 路径、缓冲池访问模式都一致。
COUNT(1) 不跳过字段读取,也不会减少 CPU 判空开销
有人误以为 COUNT(1) 省去了“读某列值”的步骤,所以更快。但事实是:它根本不需要读任何列,COUNT(*) 同样不读。两者都只依赖索引页中的行记录头(record header)判断“这一行是否存在”,连聚簇索引的 data 部分都不加载。所谓“判空”只发生在 COUNT(列名) 上——一旦括号里是具体列,优化器就必须确认该列是否为 NULL,这就可能触发整行回表或列解码。
历史版本差异已基本消失,别被老文档带偏
MyISAM 引擎下 COUNT(*) 可直接查元数据,而 COUNT(1) 需要走轻量扫描,这曾造成微小差距;但 InnoDB 自 5.7 起已统一处理,8.0+ 更彻底。现在线上几乎全是 InnoDB 表,再拿“早期 MyISAM 文档”说 COUNT(1) 更优,就像用 Windows 98 的驱动跑 Win11。
- 如果你用的是阿里云 RDS MySQL 8.0、腾讯云 CDB、或者自建 Percona Server 8.0+,
COUNT(1)和COUNT(*)的耗时差值通常在 ±0.3ms 内,远小于一次磁盘 I/O 波动 -
COUNT(id)(id 是主键)看起来和COUNT(*)一样快,不是因为它更优,而是优化器识别出NOT NULL属性后,把它重写成了COUNT(*) - 真正拖慢查询的从来不是括号里填什么,而是没主键、没索引、或
WHERE条件导致无法走覆盖索引
用 COUNT(*) 是为了语义明确和跨数据库兼容
COUNT(*) 是 SQL92 标准语法,PostgreSQL、SQL Server、Oracle、SQLite 全部原生支持且行为一致;而 COUNT(1) 虽然也通用,但某些嵌入式数据库或旧版 HiveQL 对常量表达式支持不稳。COUNT(列名) 则在语义上就不同——它统计非 NULL 行,结果可能比 COUNT(*) 少,这点在 JOIN 或视图场景下极易引发逻辑错误。线上出过真实事故:把 COUNT(user_id) 当成总用户数上报,结果因部分 user_id 为 NULL 导致漏计 12%。
括号里填什么,对性能几乎没影响;真正影响快慢的,是那张表有没有主键、二级索引是否覆盖、以及缓冲池里有没有热数据页。










