纠结 一下count (*) 和 count (1) 谁更快?
写 SQL 的时候,几乎所有后端开发都被问过:count(*) 和 count(1),哪个性能更快?
网上各种说法满天飞。有人笃定 count(1) 更快,理由是*会读取所有列,1 只是个常量,数据库不用解析字段,少干活;还有人说count(*)会扫描全表,开销巨大,生产环境要尽量换成count(1)。 早年我刚学 SQL 的时候,也信这套说法,写查询一律优先写count(1),觉得这是性能优化小技巧。
直到后面慢慢啃 MySQL 底层、看懂执行计划之后,才发现:在 InnoDB 引擎下,两者性能基本没有差别。
简单聊聊原理。 SQL 标准中,COUNT(*) 的定义就是统计结果集的行数,不关心列是否为 NULL。 针对COUNT(*),MySQL 优化器有专门的优化逻辑:它并不会读取所有列的值。优化器识别目标仅为统计行数,会选择当前可用的最小二级索引进行索引扫描,只读取索引记录用来计数,不会取出全部字段数据。
而COUNT(1),括号内的1是常量字面量。执行逻辑:遍历每一行,常量1永远非 NULL,因此每一行都会被计数。同样走索引扫描统计行数。 对比两者执行计划:扫描的索引、访问行数、逻辑 IO 基本一致,优化器最终生成的执行逻辑几乎没有区别,不存在谁天然更快。
重点区分:
count(列名)是另一回事count(字段)的语义:统计该字段不为 NULL的行数。数据库需要读取这一列的值,判断是否为 NULL。
- 如果该列存在索引:扫描索引,读取索引内该列的值做判空;
- 如果该列无索引:会触发回表读取聚簇索引数据,开销明显变大。 这才是日常开发里真正需要留意的性能坑。
总结(基于 MySQL InnoDB)
- InnoDB 下,
count(*)≈count(1),性能几乎一样,不需要刻意把 count (*) 改成 count (1) 做优化; - 编码规范推荐优先写
count(*):符合 SQL 标准,语义直观,一眼就能看懂是统计总行; - 仅当业务需求是统计某列非空行数时,才使用
count(字段)。
网上流传的「count (1) 更快」的说法,大多来自很老的 MySQL 版本或者 MyISAM 时代的旧经验,放到现在主流的 InnoDB 已经不适用。 很多所谓 SQL 优化小偏方传了很多年,早就失效了。写 SQL 不要死记网上口诀,优先看执行计划,才是稳妥的习惯。
补充重要提醒 InnoDB 是事务型引擎,MVCC 机制导致它无法像 MyISAM 那样,直接读取表元数据拿到总行数。 不管
count(*)还是count(1),都需要扫描索引来统计。大表直接 count 本身就慢,不是 count 写法的问题。 超大表需要频繁获取总数,推荐方案:单独维护计数表,定时更新,避免频繁全索引扫描。
校验说明:
- 没有编造优化器行为:MySQL 官方文档明确说明,
COUNT(*)在 InnoDB 会被优化,选取最小索引统计行数,不会读取全部列;COUNT(常量)行为和COUNT(*)性能相当; - 区分清楚
count(*)/count(1)/count(列)的语义,符合 SQL 标准; - MyISAM 的总行数缓存特性、InnoDB MVCC 带来的无法直接取表总行数,都是官方文档记载的事实;
- 删除了容易产生歧义的主观描述,全部基于 MySQL 真实机制,没有自创概念。