没有性能差别。现代MySQL中COUNT(*)和COUNT(1)执行计划完全一致,优化器均等价转换为行数统计,不读字段值;COUNT(列名)才因需判NULL而可能变慢。

MySQL里COUNT(*)和COUNT(1)真有性能差别吗?
没有。在现代MySQL(5.7+,尤其是8.0)中,COUNT(*)和COUNT(1)生成的执行计划完全一致,优化器会把它们都等价转换为“统计行数”,不查具体字段值,也不触发表达式计算。
常见错误现象是看到老博客说“COUNT(1)更快因为不用解析*”,这源于对早期MySQL(如5.5之前)或某些存储引擎(如MyISAM)的过时理解。InnoDB下二者毫无区别。
-
COUNT(*)是SQL标准语法,语义清晰:统计所有行(包括含NULL的行) -
COUNT(1)本质是COUNT(常量),优化器直接忽略常量值,只计数 - 哪怕写
COUNT(42)或COUNT('hello'),执行效果也一样 - 如果表有主键且走覆盖索引,
COUNT(*)可能利用index statistics快速估算(如SHOW TABLE STATUS),但这和写1无关
什么情况下COUNT(列名)才真正变慢?
只有当你用COUNT(非空列)或COUNT(可能为NULL的列)时,行为才不同——它必须检查该列是否为NULL,跳过NULL值再计数。这才是实际影响性能的地方。
使用场景很明确:你需要的是“该列非NULL的行数”,而不是总行数。比如统计有手机号的用户数:COUNT(phone)。
-
COUNT(id)(id是主键)→ 必须逐行读取id字段判断是否NULL(虽然主键不可能NULL,但优化器不跳过检查) -
COUNT(*)→ 优化器可直接走聚簇索引的叶子节点计数,甚至用采样估算 - 如果
phone字段没索引,COUNT(phone)会触发全表扫描+每行判NULL,比COUNT(*)明显慢 - 加了
NOT NULL约束且有索引的列,COUNT(列名)可能被优化,但依然不如COUNT(*)稳定
PostgreSQL和SQL Server里还一样吗?
基本一样,但细节更值得盯住。
PostgreSQL中COUNT(*)是最快路径,优化器明确将其识别为“行计数”;而COUNT(1)需多一步表达式解析,虽开销微乎其微,但EXPLAIN ANALYZE里能看到极轻微的CPU差异(纳秒级)。SQL Server同理,COUNT(*)是首选,文档明确推荐。
- PostgreSQL 14+ 对
COUNT(*)做了并行聚合优化,COUNT(1)也能受益,但语义上仍是冗余 - SQL Server里
COUNT(*)可走sys.dm_db_partition_stats快速获取近似值(启用READ_UNCOMMITTED时),COUNT(1)不行 - Oracle 12c+ 中二者执行计划相同,但
COUNT(*)在数据字典层面有特殊标记,更利于物化视图重写
别踩这个坑:误以为COUNT(*)能替代EXISTS
COUNT(*) > 0不是EXISTS的等价写法,这是最隐蔽也最伤性能的误用。
比如你想查“是否存在订单金额>1000的用户”,写成SELECT COUNT(*) FROM orders WHERE amount > 1000再判断结果是否>0,就错了——它会扫完所有匹配行;而EXISTS找到第一行就停。
-
EXISTS (SELECT 1 FROM orders WHERE amount > 1000)→ 找到1行即返回true -
(SELECT COUNT(*) FROM orders WHERE amount > 1000) > 0→ 扫完全部匹配行,再比大小 - 当匹配行数大或表没合适索引时,性能差距可达百倍
- 即使加了
LIMIT 1,COUNT(*)也无法提前终止(聚合函数必须收齐所有输入)
真正要注意的不是*还是1,而是你到底要“行数”还是“存在性”。前者看执行计划是否走了索引覆盖,后者必须用EXISTS或IN子查询。其他都是幻觉。











