跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

SQL如何解决COUNT(*)和COUNT(1)的区别_执行计划与性能对比

没有性能差别。现代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
子查询。其他都是幻觉。

相关文章