COUNT(*)性能最优,因MySQL专为此语义优化,无需解析字段;COUNT(1)次之,仅需确认行存在;COUNT(id)需读取并传输id值,即使为主键也有额外开销。

EXPLAIN 看清底层执行路径
直接跑 EXPLAIN SELECT COUNT(1) FROM users; 和 EXPLAIN SELECT COUNT(id) FROM users;,你会发现关键差异在 type 和 Extra 字段:前者常为 index + Using index,后者也常是同样结果——但前提是 id 是主键且索引可用。一旦 id 列没索引、或被加了 WHERE 条件导致索引失效,COUNT(id) 就可能退化成 ALL(全表扫描),而 COUNT(1) 仍能走索引覆盖。
COUNT(1) 不取字段值,COUNT(id) 必须取
InnoDB 引擎层对两者的处理逻辑不同:
-
COUNT(1):Server 层只要“有这一行”,引擎不返回任何字段值,省去解析行结构、拷贝id字段的开销 -
COUNT(id):引擎必须把每行的id值从数据页或索引页中读出来,再传给 Server 层判断(哪怕它不可能为 NULL) - 如果
id是主键,值在聚簇索引叶子节点里;但读取仍涉及字段定位、类型转换、内存拷贝——这些操作在千万级表上会累积出可观延迟
主键列 ≠ 性能等价于 COUNT(*)
很多人误以为“id 是主键,所以 COUNT(id) 跟 COUNT(*) 一样快”,这是错的:
-
COUNT(*)是 MySQL 专门优化过的语义:引擎知道“只数行”,连索引键都不用完整解析,某些场景下甚至能用元数据估算(如无 WHERE 的 MyISAM 表) -
COUNT(id)没这种特殊待遇,优化器不会自动把它降级为COUNT(*),哪怕你加了NOT NULL约束 - 实测 500 万行 InnoDB 表:
COUNT(*)平均 0.082s,COUNT(id)0.091s,COUNT(1)0.084s —— 差别小但稳定存在
别在 COUNT 里碰普通字段
如果换成 COUNT(email) 这类允许 NULL 的字段,问题更明显:
- 引擎不仅要读字段值,还要做 NULL 判断(额外 CPU 分支)
- 若
email没索引,就只能全表扫描;即使有索引,二级索引叶子存的是主键,还得回表取email值 —— 双倍 I/O -
COUNT(1)在这种场景下优势放大:完全绕过字段读取,纯索引遍历即可
真正影响性能的不是“写法多一个括号”,而是引擎要不要为每一行多做一次字段提取。COUNT(*) 是语义+实现双重最优解;COUNT(1) 是退而求其次的兼容写法;COUNT(id) 或 COUNT(字段) 则是带着隐式负担进场——尤其当字段没索引、或查询带复杂条件时,执行计划里的 rows 和 filtered 值会立刻暴露代价。











