count(*) 和 count(1) 在现代数据库中执行计划完全相同,均只遍历索引结构、不读取字段值、不判空;真正影响性能的是列是否 not null、是否有覆盖索引及 null 占比。

COUNT(*) 和 COUNT(1) 在执行计划里根本就是同一个东西
现代数据库(MySQL 8.0+、PostgreSQL、SQL Server)的优化器会把 COUNT(*) 和 COUNT(1) 统一识别为「统计行存在性」,生成完全一致的执行计划。你用 EXPLAIN 看,两者输出一模一样;用 SHOW WARNINGS 查,MySQL 甚至可能把 COUNT(*) 内部重写成 COUNT(0) —— 它们之间没有 I/O 路径、索引选择或缓冲池行为的任何区别。
别信“COUNT(1)只读一列所以更快”这种过时机理
这个说法源于早期 MyISAM 或未优化的引擎实现,现在完全不适用:
-
COUNT(*)和COUNT(1)都不会读取任何实际字段值,只遍历索引页(通常是主键 B+ 树叶子节点),每页只取索引项本身 - 哪怕表有 50 列、每行 2KB,它们也只扫索引结构,不回表、不解码、不判断 NULL
- 实测中同一张千万级 InnoDB 表上,两者的耗时差异常在 ±0.5ms 内,波动主要来自缓冲池是否命中,而非写法本身
COUNT(列名) 才是真正容易翻车的地方
它和前两者语义不同,性能表现也完全不同:
-
COUNT(department_id)必须检查该列是否为NULL,如果该列允许 NULL 且没索引,就会触发全表扫描 + 每行解码该字段 - 即使
department_id有二级索引,但 NULL 占比高(比如 60%),遍历该索引反而比扫主键更慢——因为二级索引更宽、页更多 - 误用
COUNT(email)想加速,结果 email 允许 NULL 且无索引,性能比COUNT(*)差 20%~40% -
COUNT(id)只有在id是NOT NULL主键时,优化器才可能等价处理;否则仍要逐行判空
真正影响 COUNT 性能的从来不是括号里填什么
你花时间纠结 COUNT(*) 还是 COUNT(1),不如盯住这三件事:
- WHERE 条件列有没有覆盖索引?例如
COUNT(*) WHERE status = 'active'必须有INDEX idx_status (status) - 该列是否定义了
NOT NULL?没加约束,优化器不敢跳过判空逻辑 - 要不要统计“非空”本身?先跑
COUNT(*) - COUNT(列名)看 NULL 占比,再决定是否加索引或改约束
最常被忽略的一点:NULL 是 SQL 标准强制行为,只要字段没设 NOT NULL,就存在被漏传、默认插入或应用层置空的风险——这个语义负担,COUNT(列名) 逃不掉,而 COUNT(*) 和 COUNT(1) 从一开始就不承担。










