应优先使用count(*),因其被mysql优化器深度优化;count(1)虽比count(id)轻量但非最优,后者因需读取并校验id字段值而开销更大,尤其在uuid主键或高并发场景下更明显。

MySQL执行COUNT(id)时必须读取主键字段值
在InnoDB引擎下,COUNT(id)会触发完整字段读取流程:引擎层逐行取出id列的值(哪怕它是主键),拷贝到server层,再由server层判断是否为NULL——尽管主键定义为NOT NULL,这一步校验仍不可跳过。这意味着额外的字段解析、内存拷贝和类型检查开销。
- 若
id是BIGINT自增主键,影响较小;但若是UUID字符串主键,字段长度大、解析成本高,差距更明显 - 如果表有WHERE条件,且
id不在覆盖索引范围内,可能触发回表读取整行数据 - 即使走了主键索引,InnoDB仍需解包索引项中的
id值,而非仅标记“行存在”
COUNT(1)不取字段值,只确认行存在
COUNT(1)的执行路径更轻量:InnoDB遍历过程中不提取任何列值,只向server层返回“这一行存在”的信号;server层直接累加常量1,无需判空(因为1恒非NULL)。整个过程绕过了字段读取、拷贝和NULL校验环节。
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
- 它和
COUNT(*)一样,被优化器识别为“纯行数统计”,但语义上不如COUNT(*)标准 - 在MySQL 8.0+中,两者执行计划几乎完全一致,
type通常为index(走主键索引)或ALL(全表扫描),但COUNT(1)实际I/O和CPU消耗略低 - 注意:这不是“语法糖优化”,而是引擎与server层之间明确的协作约定——server层说“我要一个不会NULL的标量”,引擎就只给“行存在”信号
别把COUNT(1)当COUNT(*)用,也别用COUNT(id)替代它们
虽然COUNT(1)比COUNT(id)快,但它仍是次优选择。真正该优先写的只有COUNT(*)——MySQL优化器对它做了最彻底的专项优化,比如跳过字段字典查找、跳过行格式解析,并在某些场景下能复用索引统计信息(如二级索引覆盖WHERE条件时)。
-
COUNT(id)容易误导后续维护者:看到id就以为在查主键有效性,其实只是凑数 -
COUNT(1)在跨数据库迁移时可能引发兼容性疑问(比如某些旧版PostgreSQL对常量表达式处理不同) - 真实性能差异在百万级以下表中几乎不可测,但语义清晰度差一点,长期看就是技术债
实际执行计划里看不出区别,但EXPLAIN FORMAT=JSON能暴露细节
用EXPLAIN FORMAT=JSON查COUNT(id)和COUNT(1),你会发现query_block -> group_by字段都为空,table -> access_type也都是index,表面一样。但深入看table -> used_columns:COUNT(id)会列出["id"],而COUNT(1)是空数组——这才是底层行为差异的铁证。
- 不要只信
EXPLAIN的rows和Extra字段,它们掩盖了字段读取层级的开销 - 高并发场景下,
COUNT(id)因多一次字段拷贝,在buffer pool压力大时更容易触发磁盘I/O - 真正要验证性能,得用
sys.schema_table_statistics查count_read和sum_number_of_bytes_read,而不是看执行时间
COUNT(*)代表你明确要行数,COUNT(id)悄悄引入了字段依赖,COUNT(1)则是个没必要的中间态——写SQL时,语义准确永远比省一个字符重要。










