COUNT()和COUNT(1)结果总是一样的,因为二者均统计所有行数且不忽略NULL;SQL标准规定COUNT()统计全部行,COUNT(1)中常量1恒非空,故语义等价,现代数据库均优化为相同执行计划。

为什么 COUNT(*) 和 COUNT(1) 结果总是一样的?
因为它们都只统计行数,不关心列值是否为 NULL。SQL 标准规定:COUNT(*) 统计所有行;COUNT(1) 中的 1 是非空常量表达式,每行都算一个有效值,所以结果必然一致。
常见错误现象:有人以为 COUNT(1) 比 COUNT(*) 快,甚至在 ORM 日志里看到它就手动改成 COUNT(1) —— 实际上现代数据库(PostgreSQL、MySQL 8.0+、SQL Server、Oracle)都会把两者优化成同一执行计划。
- MySQL 5.7+ 对
COUNT(*)有专门优化(尤其是 InnoDB 的聚簇索引行计数缓存),但COUNT(1)不会额外受益 - PostgreSQL 中两者执行计划完全相同,
EXPLAIN输出无差别 - 如果表没主键或全是可空列,
COUNT(col)(带列名)才真正有区别:它跳过NULL值
COUNT(*) 在大表上为什么还是慢?
慢不是因为函数本身,而是因为数据库必须确认“哪些行当前可见”——受事务隔离级别、未提交事务、MVCC 版本链影响。即使只是数行,引擎仍需扫描(至少是索引元数据或最小索引)来判断可见性。
使用场景:你想知道精确总数,且能接受秒级延迟(比如后台报表);但如果只是判断“有没有数据”,用 SELECT 1 FROM table LIMIT 1 更快。
- InnoDB 不保存精确总行数,
COUNT(*)默认走聚簇索引全扫(除非覆盖索引可用) - 加
LIMIT 1的存在性检查,通常毫秒内返回,避免全表/全索引扫描 - 某些场景可缓存近似值:如 MySQL 的
information_schema.TABLES.TABLE_ROWS,但它是估算值,不准
什么时候该用 COUNT(col) 而不是 COUNT(*)?
只当你明确需要“该列非 NULL 的行数”时才用。它和前两者语义不同,不是性能替代方案。
常见错误现象:把 COUNT(id) 当作“更快的 COUNT(*)”用,结果发现结果变少了(比如 id 允许为 NULL),或者误以为能走更窄索引而提速——实际优化器可能仍选聚簇索引。
-
COUNT(col)会过滤掉col IS NULL的行,结果 ≤COUNT(*) - 若
col是主键或定义为NOT NULL,则COUNT(col) = COUNT(*),但写法反而降低可读性 - 想靠它提升性能?只有当该列有非空约束 + 存在更小的二级索引时,优化器才可能选择扫描那个索引——但不保证,且收益有限
ORM 或分页场景下怎么避免重复 COUNT?
典型坑:先 SELECT COUNT(*) 得总数,再 SELECT ... LIMIT offset, size 查数据——两次查询、两次扫描,大表上开销翻倍。
真实需求往往是“查第 N 页,同时知道总共有多少页”。这时候要么用窗口函数(如 COUNT(*) OVER()),要么接受前端分页不显示总页数(更合理),要么加缓存层预估。
- PostgreSQL 支持
SELECT *, COUNT(*) OVER() AS total FROM t LIMIT 20 OFFSET 40,一次扫描完成 - MySQL 8.0+ 同样支持窗口函数,但注意
OVER()无分区时会强制物化中间结果,内存占用可能升高 - 如果只是做“加载更多”,根本不需要总数——用基于游标的分页(
WHERE id > last_seen_id LIMIT 20)更稳定、更快










