count(1)、count(*) 与 count(列名) 的区别?
count(1)、count(*) 与 count(列名) 的区别?
zhangchangsheng面试考察点
- SQL 基础理解:面试官不仅仅是想知道 “有什么区别”,更是想考察你是否理解
COUNT()函数的语义——统计的是 行数 还是 非 NULL 值的数量。 - NULL 值处理:考察你是否清楚
count(列名)会忽略 NULL 值,而count(*)和count(1)不会,这是很多面试者容易踩的坑。 - 性能优化意识:考察你是否了解不同写法在 MySQL 不同版本中的执行效率差异,以及 InnoDB 对
count(*)的优化机制。
核心答案
三种 COUNT 方式的语义对比:
| 写法 | 统计内容 | NULL 值处理 | 执行效率 |
|---|---|---|---|
| count(*) | 统计 总行数 | 不忽略 NULL | ⭐⭐⭐⭐⭐ 最优(MySQL 优化) |
| count(1) | 统计 总行数 | 不忽略 NULL | ⭐⭐⭐⭐ 等同于 count(*) |
| count(列名) | 统计 该列非 NULL 的行数 | 忽略 NULL | ⭐⭐⭐ 需要判断列值 |
一句话总结:count(*) 和 count(1) 效果相同,统计所有行;count(列名) 只统计该列 非 NULL 的行数。推荐使用 count(*)。
深度解析
一、核心区别:NULL 值处理
这是三种写法最本质的区别:
-- 测试表 |
示例表数据(共 4 行):
| id | name | age |
|---|---|---|
| 1 | Alice | 25 |
| 2 | Bob | NULL |
| 3 | NULL | 30 |
| 4 | NULL | NULL |
执行结果:
| SQL 写法 | 结果 | 说明 |
|---|---|---|
| count(*) | 4 | 统计所有行,不管 NULL |
| count(1) | 4 | 统计所有行,不管 NULL |
| count(name) | 2 | 只统计 name 非 NULL 的行 |
| count(age) | 2 | 只统计 age 非 NULL 的行 |
关键:count(列名) 会忽略 NULL 值!
上图展示了三种 COUNT 方式对 NULL 值的处理差异。核心要点:
count(*):统计表的 总行数,不管列值是否为 NULLcount(1):语义上是 “统计每一行 1 这个表达式非 NULL 的数量”,实际上 MySQL 会优化成和count(*)一样count(列名):统计该列 非 NULL 值的行数,会跳过 NULL 值
二、执行效率对比
流传的误区:很多人认为 count(1) 比 count(*) 快,因为 count(*) 会扫描所有列。 实际情况:在现代 MySQL(5.7+)中,两者效率 完全相同,甚至 count(*) 更优。
| 对比项 | MyISAM | InnoDB |
|---|---|---|
| 总行数存储 | 表元数据中存储了总行数 | 由于 MVCC 机制,不同事务看到的行数可能不同,无法在元数据中存储”准确”的总行数 |
| count(*) 执行方式 | 直接读取元数据,O(1) 时间复杂度 | 扫描索引统计,O(N) 时间复杂度 |
| 速度 | 非常快 | 慢,但保证事务隔离级别下的正确性 |
| 适用限制 | 只适用于不带 WHERE 条件的 count(*) | 无限制 |
为什么 InnoDB 不能像 MyISAM 一样存总行数:事务 A(未提交的 DELETE)删除了 10 行后,事务 B 的 count() 看到的是原行数,事务 A 提交后事务 C 的 count() 看到的是删除后的行数——不同事务看到的结果不同,无法存储统一的行数。
为什么 count(*) 更快?
- MySQL 专门优化:InnoDB 对
count(*)做了特殊优化,会自动选择 最小的辅助索引 进行扫描 - 不扫描全部列:
count(*)不会读取所有列数据,只统计行数 count(1)会被优化成count(*),两者执行计划完全相同
三、为什么 InnoDB 的 count(*) 这么慢?
很多人发现 InnoDB 的 count(*) 比 MyISAM 慢很多,这是因为:
| 写法 | 优化器处理(MySQL 5.7+) | 效率 |
|---|---|---|
| count(*) | 优先选择成本最小的辅助索引:优先扫描最小的二级索引(而非聚簇索引),因为二级索引更小、I/O 更少;没有二级索引时才扫描聚簇索引 | 最高 |
| count(1) | MySQL 会将 count(1) 优化为 count(*),执行计划完全相同 | 与 count(*) 相同 |
| count(列名) | 需要判断每一行的该列是否为 NULL:该列有索引则用索引扫描,无索引则全表扫描 + NULL 判断 | 略低于 count(*) |
四、大表 count 优化方案
对于 InnoDB 大表的 count 查询,可以考虑以下优化方案: 方案一:使用缓存
// 使用 Redis 缓存总数 |
方案二:维护计数表
-- 创建计数表 |
方案三:估算(不要求精确)
-- 使用 EXPLAIN 估算 |
五、使用场景建议
| 场景 | 推荐写法 | 原因 |
|---|---|---|
| 统计总行数 | count(*) | 语义清晰、MySQL 优化、推荐写法 |
| 统计某列非空数量 | count(列名) | 语义明确,会忽略 NULL |
| 联合统计 | count(DISTINCT 列名) | 统计去重后的非 NULL 数量 |
| 条件统计 | SUM(CASE WHEN 条件 THEN 1 ELSE 0 END) | 更灵活的条件统计 |
-- 推荐写法 |
面试高频追问
- 追问一:为什么 InnoDB 的
count(*)比 MyISAM 慢?- 答:MyISAM 在表元数据中存储了总行数,
count(*)直接读取即可,O(1) 复杂度;InnoDB 由于 MVCC 机制,不同事务看到的行数可能不同,无法存储统一的计数,需要扫描索引统计,O(N) 复杂度。
- 答:MyISAM 在表元数据中存储了总行数,
- 追问二:
count(id)和count(*)哪个快?- 答:
count(*)更快。因为count(*)会选择最小的辅助索引扫描;而count(id)虽然主键上有索引,但 MySQL 不一定会选择它(除非它是最小的索引)。而且count(id)还需要判断 NULL 值。
- 答:
- 追问三:如何优化大表的
count(*)查询?- 答:1)使用 Redis 缓存计数结果;2)维护独立的计数表;3)使用估算(EXPLAIN 的 rows);4)确保有合适的辅助索引供 MySQL 选择。
常见面试变体
- “
count(*)和count(1)哪个效率高?” - “为什么
count(列名)结果比count(*)少?” - “如何优化 InnoDB 大表的 count 查询?”
- “
count(DISTINCT 列名)是什么意思?”
记忆口诀
COUNT 三剑客:
- 星号统计行:
count(*)统计所有行,MySQL 优化最快 - 数字效果同:
count(1)等同于count(*) - 列名忽略空:
count(列名)只统计非 NULL 值
总结
count(*) 和 count(1) 效果相同,统计所有行数,MySQL 5.7+ 会将两者优化成相同的执行计划,推荐使用语义更清晰的 count(*);count(列名) 只统计该列 非 NULL 的行数。InnoDB 由于 MVCC 机制无法像 MyISAM 一样存储精确行数,大表 count 需要通过缓存或计数表优化。
声明:本文转载自小哈的博客 《count(1)、count(*) 与 count(列名) 的区别?》,版权归原文作者所有。转载请保留本声明与原文链接。
