count(*)和count(1)在innodb下执行计划完全一样,优化器统一重写为最小二级索引或聚簇索引扫描,不读行数据、只计索引条目数;count(列名)才真正影响性能,因其需逐行判null。

COUNT(*) 和 COUNT(1) 在 InnoDB 下执行计划完全一样
MySQL 8.0+ 的 InnoDB 引擎对 COUNT(*) 和 COUNT(1) 做了统一优化:优化器会把两者都重写为同一执行路径,实际都走最小可用二级索引(或聚簇索引)扫描,不读取行数据,只统计索引条目数。你用 EXPLAIN 查看,结果一模一样:
EXPLAIN SELECT COUNT(*) FROM orders; EXPLAIN SELECT COUNT(1) FROM orders;
两者的 type 都是 index,key 指向同一个索引,rows 完全一致。
COUNT(1) 不比 COUNT(*) 快,甚至可能多一次常量解析
虽然语义等价,但 COUNT(1) 需要 Server 层把常量 1 注入执行上下文,而 COUNT(*) 是语法特例,直接触发引擎层的快速计数逻辑。实测百万级表,差异在毫秒级波动范围内,属于测量噪声——不是“基本没差别”,而是“在可观测层面无差别”。
-
COUNT(*)是 SQL92 标准写法,跨数据库兼容性更好(PostgreSQL、SQL Server、Oracle 都支持且行为一致) -
COUNT(1)在某些旧版 MySQL(如 5.6 之前)或非 InnoDB 引擎(如 MyISAM)中曾有细微差异,但现在已无实际意义 - 如果表没有二级索引,两者都会退化为聚簇索引扫描,性能完全相同
COUNT(列名) 才是真·慢操作,别误用
真正影响性能的是 COUNT(某一列),尤其当该列允许 NULL 时:
- 必须逐行读取该列值并判断是否为
NULL,无法跳过数据页 - 若列无索引,强制全表扫描;即使有索引,也要回表或检查索引值是否为空
- 比如
COUNT(status)(status是VARCHAR且允许NULL),比COUNT(*)慢 40%~100%,取决于NULL比例
注意:COUNT(id)(id 是主键)虽快于普通列,但仍需读取主键值,略慢于 COUNT(*) —— 因为 COUNT(*) 连主键都不读,只数索引节点。
线上用 COUNT(*) 就完事,但得盯住 WHERE 条件
真正拖慢 COUNT 查询的,从来不是 * 还是 1,而是 WHERE 子句有没有走索引:
- 带
WHERE的COUNT(*)如果无法利用索引下推(ICP)或索引覆盖,就会变成慢查询 - 例如
COUNT(*) FROM logs WHERE created_at > '2025-01-01',若created_at无索引,就是全表扫描 - 此时优化重点是加索引、改写条件、或用近似统计(如
SHOW TABLE STATUS中的Rows字段,仅作参考)
别在 * 和 1 上纠结,先确认执行计划里有没有 type: index 或 range —— 没有,就说明问题不在 COUNT 写法本身。











