count(*)与count(1)在mysql 8.0+ innodb中性能完全一致,优化器统一重写为扫描最小索引、不读行数据;真正影响性能的是count(列名)需判空及where条件是否走索引。

MySQL 8.0+ 的优化器会把 COUNT(1) 和 COUNT(*) 统一重写为同一执行路径
InnoDB 引擎在语义分析阶段就识别出 1 是非空常量,直接将其归一化为 COUNT(*) 的内部表示。后续所有环节——索引选择、扫描范围、I/O 策略、是否回表——完全一致。
你用 EXPLAIN 查看两者执行计划,会发现:
-
type都是index -
key指向同一个索引(通常是主键或最小二级索引) -
rows值完全相同
这不是“巧合”,是优化器设计使然
COUNT(1) 实际还多一次常量解析开销
COUNT(*) 是 SQL92 标准语法特例,MySQL Server 层可直通引擎层的快速计数逻辑;而 COUNT(1) 需要把数字 1 注入执行上下文,多一次常量绑定操作。
实测百万级 InnoDB 表,两者耗时差值常在 ±0.5ms 内,波动幅度小于缓冲池命中率变化带来的影响。所谓“更快”,属于测量噪声,不是可观测差异
真正拖慢 COUNT 查询的,从来不是括号里填什么
-
COUNT(status)(status允许 NULL):必须逐行读取该字段并判空,若无索引,就是全表扫描 -
COUNT(*) FROM logs WHERE created_at > '2025-01-01':若created_at没索引,type是ALL,此时优化重点是加索引,不是换写法 - 即使
COUNT(id)(id是主键),也要读取主键值,比COUNT(*)多一次字段读取,略慢
别在 COUNT(1) 和 COUNT(*) 之间做取舍
它们在执行引擎眼里是同一个东西。真正要盯紧的,是:
- 表有没有主键(InnoDB 强制要求,没主键会退化为更重的扫描)
-
WHERE条件列是否有覆盖索引 - 你到底想统计“行存在性”还是“某列非空性”——后者才是性能分水岭
复杂点在于:很多人看到 EXPLAIN 显示 type: index 就以为没问题,但没注意 key 指向的是不是最小索引,也没确认 Extra 里有没有 Using index。这些细节才决定实际 I/O 成本。











