COUNT() 和 COUNT(1) 在现代 MySQL(5.7+)中性能完全一致,MySQL 8.0+ 已深度优化 COUNT(),InnoDB 直接返回聚簇索引行数,无需逐行扫描;COUNT(1) 语义上仍需表达式求值,存在微小解析开销;官方推荐使用 COUNT(*)。

COUNT(1) 和 COUNT(*) 在现代 MySQL(5.7+)中性能完全一致,不存在“一定更快”的情况;所谓“COUNT(1) 更快”是过时经验或误读执行计划导致的错觉。
MySQL 8.0+ 中 COUNT(*) 已被深度优化,不再需要“手动替换”为 COUNT(1)
MySQL Server 层对 COUNT(*) 有专用路径优化:InnoDB 存储引擎直接返回聚簇索引(主键索引)的行数估算或精确值(取决于事务隔离级别和是否加锁),根本不会逐行扫描或读取字段内容。而 COUNT(1) 虽然也被优化为同样路径,但它的语义是“对每行计算常量 1 是否非 NULL”,仍需触发表达式求值逻辑——哪怕这个逻辑已被内联消除,也多一层语义解析开销。
实测中两者执行计划(EXPLAIN)完全相同,Handler_read_next、innodb_rows_read 等指标无差异。官方文档明确指出:COUNT(*) 是标准且首选写法。
COUNT(1) 在无主键表里“看似更快”其实是假象
当表没有主键(即使用隐式 ROWID 或 MyISAM 引擎),部分旧版 MySQL 可能对 COUNT(1) 选择更紧凑的遍历方式(如仅扫描 .MYI 索引文件),而 COUNT(*) 可能退化为全表扫描。但这不是优化,而是引擎能力缺失下的权宜之计。
- 这种差异在 InnoDB + 显式主键下已不复存在
- 即使存在,也是因
COUNT(*)触发了更保守的统计路径(如加SELECT FOR UPDATE锁时),而非性能缺陷 - 依赖这种“优势”会让 SQL 在迁移至其他引擎(如 TiDB、PostgreSQL)时失效
COUNT(列名) 和 COUNT(*) 的行为差异才是真坑点
很多人拿 COUNT(1) 对比 COUNT(email) 得出“1 更快”,却忽略了二者根本不是同一类操作:
-
COUNT(*)和COUNT(1)统计所有行(含email IS NULL的行) -
COUNT(email)必须检查每行email是否为NULL,哪怕该列有索引,也无法跳过空值判断 - 若
email列允许 NULL 且实际 NULL 值多,COUNT(email)可能比COUNT(*)慢 2–3 倍 - LEFT JOIN 后对右表字段用
COUNT(右表.id),结果常为 0 —— 因为没匹配上的行,该字段就是NULL
真正影响 COUNT 性能的是索引结构,不是括号里的值
决定速度的是“扫描哪棵树”,而不是写 1 还是 *:
- 有主键 →
COUNT(*)/COUNT(1)/COUNT(主键)都走聚簇索引,最快 - 只有二级索引(如
INDEX idx_status(status))→COUNT(status)可能走该索引,但需过滤 NULL;COUNT(*)仍走聚簇索引 - 无索引 → 全表扫描不可避免,
COUNT(*)和COUNT(1)开销相同,COUNT(大文本列)反而更慢(要读取列内容)
别纠结 1 还是 *,先看执行计划里 key 字段选了哪个索引;如果发现 type: ALL,那括号里写什么都救不了性能。

















