count(1)与count()在现代数据库中性能完全等价,优化器在语义分析阶段即识别1为非空常量,统一重写为count()并生成相同执行计划;所谓“count(1)更快”源于过时版本或误判。

因为现代数据库优化器在语义分析阶段就识别出 COUNT(1) 中的 1 是非空常量,直接将其重写为 COUNT(*),后续所有执行路径(索引选择、扫描范围、并行策略)完全一致。
优化器如何处理 COUNT(1) 和 COUNT(*)
主流数据库(MySQL 8.0+、PostgreSQL 14+、Oracle 12c+、SQL Server)在解析 SQL 时,会把 COUNT(1) 当作一个“无实际列依赖的确定性表达式”:它不读任何字段、不检查 NULL、不触发回表。优化器立刻推导出其逻辑等价于“统计所有被 WHERE 选中的行”,于是统一归一化为 COUNT(*) 的内部表示。你用 EXPLAIN 或 EXPLAIN ANALYZE 看到的执行计划,两个写法的 type、key、rows、Extra 字段几乎完全相同——这不是巧合,是设计使然。
为什么老说法说 COUNT(1) 更快?
那些说法基本来自三类已过时的上下文:
- MySQL 5.5 及更早版本中,
COUNT(*)的实现曾依赖handler::records接口,而 MyISAM 引擎缓存了精确行数;但某些场景下COUNT(1)被误判为“需扫描”,反而绕过了这个缓存 bug(实为缺陷,非优势) - 早期 SQLite(COUNT(*) 做常量折叠,但把
COUNT(1)当作表达式提前求值,造成微小差异 - ORM 框架(如旧版 Hibernate)在生成 HQL 时对
COUNT(*)解析异常,强制替换成COUNT(1)才能通过语法校验
这些都不是性能优化,而是历史兼容性补丁。
COUNT(列名) 才是真·性能分水岭
真正影响执行效率的,从来不是 * 还是 1,而是你有没有写成 COUNT(列名):
-
COUNT(id)如果id允许 NULL,优化器无法跳过 NULL 判断,哪怕有索引也得逐行检查 -
COUNT(*)和COUNT(1)都可走最小索引(比如只含主键的 B+ 树叶子节点),I/O 最小化 - 在 InnoDB 中,
COUNT(*)优先扫描PRIMARY KEY的索引页计数;而COUNT(email)若email无 NOT NULL 约束,可能被迫回表或走更宽的二级索引
别在 COUNT(1) 和 COUNT(*) 之间做取舍——它们在执行引擎眼里是同一个东西。真正要盯紧的,是表有没有主键、WHERE 条件能否命中覆盖索引、以及你到底想统计“物理行数”还是“某列非空行数”。后者才是影响性能的硬边界。











