mysql 8.0+中count()与count(1)执行计划完全相同,优化器均重写为扫描最小索引,不读行数据;count(1)反有多一次常量注入开销,语义和性能上count()更优。

MySQL 8.0+ 中 COUNT(1) 和 COUNT(*) 执行计划完全一样
你执行 EXPLAIN SELECT COUNT(*) FROM orders 和 EXPLAIN SELECT COUNT(1) FROM orders,会发现 type 都是 index,key 指向同一个索引,rows 值也一致。这不是巧合——InnoDB 优化器在解析阶段就将两者统一重写为“扫描最小可用索引(通常是主键或最窄二级索引)”,不读取行数据,只数索引节点数。
常见错误现象:看到某次压测中 COUNT(1) 耗时低 2ms,就认为它更快 → 实际是缓冲池命中、页缓存抖动或并发查询干扰造成的测量噪声,不是函数本身差异。
真正影响执行路径的,从来不是括号里写 * 还是 1,而是表引擎、索引结构和是否带 WHERE 条件。
COUNT(1) 反而多一次常量注入开销
COUNT(*) 是 SQL 标准语法特例,优化器识别后直接触发引擎层的快速计数逻辑;而 COUNT(1) 需 Server 层把常量 1 注入执行上下文,再交由引擎处理。虽然这个开销在毫秒级内不可测,但语义上它确实比 COUNT(*) 多一个解析步骤。
使用场景注意点:
- MyISAM 表无
WHERE条件时,两者都走元数据直取,快得没差别 - InnoDB 表即使没有二级索引,也会退化为聚簇索引扫描,性能仍相同
- 如果真有“
COUNT(1)更快”的案例,大概率是旧版 MySQL(如 5.6 之前)或非 InnoDB 引擎(如 Memory),现在已无实际参考价值
COUNT(列名) 才是真正的性能分水岭
真正拖慢统计速度的,是 COUNT(status) 这类写法:
- 若
status允许NULL且无索引 → 必须全表扫描 + 每行读取并判空 - 即使
status有索引,数据库仍需遍历索引确认哪些条目非NULL(因为 B+ 树索引默认不存NULL值,但覆盖性不确定) -
COUNT(id)(id是主键)虽略快于普通列,但仍需读取主键值,不如COUNT(*)连主键都不读来得干脆
参数差异关键点:COUNT(*) 统计行存在性;COUNT(列名) 统计该列非 NULL 值数量,行为依赖列定义和索引状态。
线上该用哪个?盯住 WHERE,别盯括号里写啥
真正决定 COUNT 查询快慢的,是 WHERE 条件能否走索引:
-
COUNT(*) FROM logs WHERE created_at > '2025-01-01'如果created_at没索引 → 全表扫描,再快的COUNT写法也救不了 - 优化重点永远是加索引、改写条件、或用近似值(如
SHOW TABLE STATUS中的Rows字段,仅作参考) -
COUNT(*)是 SQL92 标准,PostgreSQL/Oracle/SQL Server 行为一致;COUNT(1)容易让新人误解为“检查某列是否等于 1”
别在 * 和 1 上纠结,先看 EXPLAIN 输出里有没有 type: index 或 range —— 没有,问题就不在 COUNT 写法本身。










