count(*)比count(字段)快,根本原因是前者无需读取字段值、可走最小索引遍历,而后者必须逐行取出字段并判空,可能触发回表或全行解析,i/o开销更大。

count(*) 为什么比 count(字段) 快?
根本原因在于:InnoDB 不需要读取实际字段值,就能完成 count(*);而 count(字段) 必须逐行取出该字段内容,并判断是否为 NULL。哪怕字段只是 VARCHAR(1),只要不是主键或索引覆盖列,InnoDB 就得回表或解析整行数据。
更关键的是,count(*) 被 MySQL 优化器特殊识别——它知道语义是“统计行数”,于是会主动选择最小的可用索引(比如一个只存主键 ID 的二级索引)来遍历,避免扫描聚簇索引(即整行数据)。而 count(字段) 没有这种优化,server 层明确要那个字段,InnoDB 就只能给那个字段。
count(*) 和 count(1) 真的等价吗?
逻辑上等价,但执行路径略有差异:
-
count(*):优化器直接触发“行计数”路径,优先走最小索引,不取任何字段值 -
count(1):InnoDB 仍需遍历索引,但 server 层对每行硬塞一个常量1,再判断非空(恒真),所以也不取字段值
两者都不访问数据行,性能几乎无差别。但注意:count(1) 在某些老版本或未 ANALYZE 表时,可能错过索引选择优化;而 count(*) 的语义更清晰,优化器更敢放手优化。
为什么 count(主键) 有时比 count(*) 还快?
仅当满足以下全部条件时才成立:
- 表有主键(InnoDB 必然有,但显式定义很重要)
- 查询无
WHERE条件(否则无法利用索引快速跳过) - 主键是单列且类型紧凑(如
BIGINT),而你又恰好建了更小的二级索引(比如INDEX idx_flag (status))
这时 count(主键) 可能被优化为扫描该主键索引(B+ 树叶子节点只存主键值),而 count(*) 若误判索引大小,反而选了稍大的二级索引。但现实中这种差距通常在毫秒级,且不可控——不如直接信 count(*) 的语义稳定性。
count(字段) 最容易踩的坑
三个典型问题:
- 字段含
NULL:比如SELECT COUNT(updated_at) FROM audit_log,若某条记录updated_at IS NULL,这条就不计入——结果比真实行数少,且难以察觉 - 字段没索引:InnoDB 不得不走聚簇索引,把整行数据都读出来,只为取一个字段判断是否为空,I/O 开销翻倍
- 字段类型大:如
TEXT或长VARCHAR,即使有索引,也可能因索引不覆盖而强制回表,放大延迟
除非业务明确要求「该字段非空的数量」,否则别用 count(字段) 统计总行数。它既慢,又容易错——尤其是当你后来给该字段加了允许 NULL 的约束时,查询结果就静默变了。











