MySQL 8.0+ 中 COUNT() 和 COUNT(1) 执行计划完全一致,因优化器在语义分析阶段将 COUNT(1) 归一化为 COUNT(),后续路径完全相同;仅极少数旧场景下 ANALYZE TABLE 后的小表可能有微小差异。

MySQL 8.0+ 的执行计划完全一样
你用 EXPLAIN 看 COUNT(*) 和 COUNT(1),type、key、rows、Extra 字段几乎完全一致。这不是巧合,是优化器在语义分析阶段就做了归一化:把 COUNT(1) 识别为“确定性非空常量”,直接重写成内部等价的 COUNT(*) 表示,后续所有路径(索引选择、并行策略、I/O 范围)都走同一套逻辑。
COUNT(1) 比 COUNT(*) 慢的极少数情况
只在非常特定的旧场景下才可能观察到微小差异:
-
COUNT(1)在刚执行过ANALYZE TABLE的小表( - 误写成
COUNT(0):某些 MySQL 5.6 之前版本会把它当作可为空表达式,触发全表扫描 - ORM 框架(如老版 Hibernate)强制替换
COUNT(*)为COUNT(1)是为了绕过语法校验,不是性能优化
COUNT(列名) 才是真正的性能分水岭
真正影响速度的从来不是括号里填 * 还是 1,而是你有没有写成 COUNT(列名):
-
COUNT(id)如果id是主键(NOT NULL),优化器通常能等价处理,和COUNT(*)一样快 -
COUNT(email)如果email允许 NULL 且没索引,就必须逐行读取并判空——哪怕只差一个字节,也得回表或解码整行 - LEFT JOIN 后用
COUNT(o.id)想统计关联数?它会漏掉o.id IS NULL的行,语义已偏移,还拖慢执行
别被“解析开销”说法骗了
所谓“* 要解析所有列”是过时认知。现代 SQL 解析器对 COUNT(*) 有专用快速路径,比解析数字常量还轻量。真正该盯紧的只有三件事:
- WHERE 条件能否命中覆盖索引
- 表是否有主键(InnoDB 强制要求,也是 COUNT 最小扫描路径的基础)
- 你到底想统计“行存在性”还是“某列非空性”——后者才是语义和性能的双重边界


















