乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 数据库基础与安装

    • MySQL 简介
    • MySQL 5.6 到 5.7 到 8.0 关键差异速查
    • 安装 MySQL 5.7
    • 连接与断开服务器
    • 创建数据库
    • 创建数据表
    • 数据库与数据表
    • 加载数据
    • 获取数据库信息
    • 批处理模式
    • SHOW 语句汇总
    • FLUSH 与 RESET 语句
    • my.cnf 核心参数
    • 字符集与排序规则
  • 第2章 SQL基础查询

    • SELECT
    • WHERE
    • ORDER BY
    • LIMIT
    • COUNT
    • 聚合函数
    • 比较运算符
    • 逻辑运算符
    • 算术运算符
    • 模式匹配
    • NULL 值处理
    • UPDATE
    • DELETE
    • REPLACE
    • SELECT INTO
  • 第3章 数据类型与运算符

    • 数值类型
    • 字符串类型
    • 日期时间类型
    • BIT 类型
    • ENUM 类型
    • SET 类型
    • JSON 类型
    • 类型转换
  • 第4章 函数与表达式

    • 字符串函数
    • 数值函数
    • 日期函数
    • 全文检索函数
  • 第5章 高级查询与子查询

    • JOIN
    • 子查询
    • UNION
    • GROUP BY
    • HAVING
    • DISTINCT
  • 第6章 表与索引

    • 数据定义语言
    • 修改表结构
    • 视图
    • 修改视图与检查选项
    • 外键
    • 索引
    • 唯一索引
    • 复合索引
    • 存储引擎对比
    • 分区表
    • 第一范式与第二范式
    • 第三范式与 BC 范式
    • 反范式设计
  • 第7章 存储过程与函数

    • 存储过程
    • 存储函数
    • 变量
    • 流程控制
    • 游标
    • 预处理语句
  • 第8章 事务与锁

    • 事务
    • 事务隔离级别
    • 锁机制
    • MVCC
    • 死锁专题分析
    • LOCK TABLES
    • XA 事务
  • 第9章 用户管理与安全

    • 用户管理
    • 权限管理
    • 角色
    • SQL 注入防范
  • 第10章 性能优化入门

    • 执行计划
    • 索引优化
    • 查询优化
    • 查询优化器提示
    • 慢查询日志
    • InnoDB 深入机制
    • InnoDB 专项优化
    • Performance Schema
    • sys Schema
  • 第11章 复制与高可用

    • 主从复制原理
    • 半同步复制配置
    • binlog 开启与 point-in-time 恢复
    • mysqldump 全库备份
    • mysqldump 单表与条件备份
    • mysqldump 恢复与导入
    • xtrabackup 全量热备
    • xtrabackup 准备与恢复
    • xtrabackup 增量与流式备份
  • 第12章 触发器与事件

    • 触发器
    • 事件调度器
  • 参考

    • MySQL 5.7 专业术语大全
    • MySQL 5.7 关键字与保留字大全

COUNT

导学

COUNT 是 SQL 中使用频率最高的聚合函数,也是面试中的"重灾区"。COUNT(*)、COUNT(1)、COUNT(列) 有什么区别?为什么 COUNT(*) 有时很慢?本节把 COUNT 彻底讲透。

定义

COUNT:聚合函数,用于统计行数或非 NULL 值的数量。根据参数不同,行为差异很大。

演示数据准备

DROP TABLE IF EXISTS employees;

CREATE TABLE employees (
    emp_id INT PRIMARY KEY AUTO_INCREMENT,
    emp_name VARCHAR(20),
    dept VARCHAR(20),
    score DECIMAL(5,2)
);

INSERT INTO employees (emp_name, dept, score) VALUES
('大翔', '技术部', 100),
('白歌', '技术部', NULL);

当前 employees 表中的完整数据如下:

emp_idemp_namedeptscore
1大翔技术部100
2白歌技术部NULL

SQL 示例

场景一:COUNT(*) — 统计所有行

当前数据状态:见上文 employees 表完整数据。

执行语句:

SELECT COUNT(*) FROM employees;

操作后结果:

COUNT(*)
2

结果解读:统计 employees 表的总行数,包括所有列都为 NULL 的行(如果有的话)。这是获取表总行数的标准写法。COUNT(*) 不会展开所有列,而是有特殊的优化路径。

场景二:COUNT(列) — 统计非 NULL 行

当前数据状态:见上文 employees 表完整数据。

执行语句:

SELECT COUNT(score) FROM employees;

操作后结果:

COUNT(score)
1

结果解读:只统计 score 列不为 NULL的行。白歌的 score 为 NULL,因此被排除,返回 1。用于统计"已录入绩效分的员工数"。

再看 COUNT(emp_id):

SELECT COUNT(emp_id) FROM employees;

操作后结果:

COUNT(emp_id)
2

结果解读:emp_id 是主键,不可能为 NULL,因此 COUNT(emp_id) 等同于 COUNT(*),返回 2。

场景三:COUNT(DISTINCT 列) — 去重计数

当前数据状态:见上文 employees 表完整数据。

执行语句:

SELECT COUNT(DISTINCT dept) FROM employees;

操作后结果:

COUNT(DISTINCT dept)
1

结果解读:先对 dept 去重,再统计不同值的数量。employees 表中只有 1 种部门(技术部),因此返回 1。用于统计"有多少种不同的部门"。

也可以对多个列的组合去重:

SELECT COUNT(DISTINCT dept, score) FROM employees;

操作后结果:

COUNT(DISTINCT dept, score)
2

结果解读:(技术部, 100)、(技术部, NULL) 被视为不同的组合。表中实际存在的组合有 2 种,因此返回 2。

场景四:COUNT(*) 与 WHERE 结合

当前数据状态:见上文 employees 表完整数据。

执行语句:

SELECT COUNT(*) FROM employees WHERE dept = '技术部';

操作后结果:

COUNT(*)
2

结果解读:先过滤 dept = '技术部' 的行(大翔、白歌),再计数。只统计满足条件的行数。

再看一个多条件结合的例子:

SELECT COUNT(*) FROM employees WHERE dept = '技术部' AND score >= 90;

操作后结果:

COUNT(*)
1

结果解读:技术部部门的员工有大翔、白歌,其中大翔的 score >= 90(白歌为 NULL),因此返回 1。

场景五:同时获取总数和条件计数(CASE WHEN + COUNT)

当前数据状态:见上文 employees 表完整数据。

执行语句:

SELECT
    COUNT(*) AS 总人数,
    COUNT(CASE WHEN dept = '技术部' THEN 1 END) AS 技术部人数,
    COUNT(CASE WHEN dept = '产品部' THEN 1 END) AS 产品部人数,
    COUNT(CASE WHEN score >= 90 THEN 1 END) AS 优秀人数,
    COUNT(CASE WHEN score IS NULL THEN 1 END) AS 绩效分未录入人数,
    COUNT(CASE WHEN dept = '运营部' THEN 1 END) AS 运营部人数
FROM employees;

操作后结果:

总人数技术部人数产品部人数优秀人数绩效分未录入人数运营部人数
220110

结果解读:CASE WHEN 配合 COUNT 是单查询实现条件统计的经典技巧。不满足条件的行返回 NULL,COUNT 忽略 NULL,因此只统计满足条件的行。

  • 总人数:2
  • 技术部人数:大翔、白歌 = 2
  • 产品部人数:0(没有产品部员工)
  • 优秀人数(score >= 90):大翔 100 = 1(白歌 NULL 不满足)
  • 绩效分未录入人数(score IS NULL):白歌 = 1
  • 运营部人数:0(没有运营部员工)

场景六:COUNT(1) 与 COUNT(*) 的对比

当前数据状态:见上文 employees 表完整数据。

执行语句:

SELECT COUNT(*) FROM employees;
SELECT COUNT(1) FROM employees;
SELECT COUNT(0) FROM employees;
SELECT COUNT('abc') FROM employees;

操作后结果:

COUNT(*)COUNT(1)COUNT(0)COUNT('abc')
2222

结果解读:在 MySQL 5.7 中,COUNT(*)、COUNT(1)、COUNT(0)、COUNT('abc') 的结果完全相同。优化器会同等对待这些写法,都会统计所有行数。COUNT(1) 中的 1 只是一个常量表达式,对每一行都计算为 1,然后统计非 NULL 的行数(常量永远非 NULL),因此等同于 COUNT(*)。

场景七:COUNT 与 NULL 的陷阱

当前数据状态:见上文 employees 表完整数据(白歌的 score 为 NULL)。

执行语句:

SELECT
    COUNT(*) AS 总人数,
    COUNT(score) AS 有绩效分人数,
    COUNT(*) - COUNT(score) AS 无绩效分人数
FROM employees;

操作后结果:

总人数有绩效分人数无绩效分人数
211

结果解读:COUNT(*) 统计所有员工(2 条),COUNT(score) 只统计 score 非 NULL 的员工(1 条)。两者相减得到 score 为 NULL 的员工数(1 条:白歌)。

场景八:空表的 COUNT 行为

当前数据状态:创建一张空表。

CREATE TABLE IF NOT EXISTS empty_employees (
    emp_id INT PRIMARY KEY,
    emp_name VARCHAR(20)
);

执行语句:

SELECT COUNT(*) FROM empty_employees;
SELECT COUNT(emp_name) FROM empty_employees;

操作后结果:

COUNT(*)COUNT(emp_name)
00

结果解读:空表没有任何行,COUNT(*) 和 COUNT(列) 都返回 0。这是 COUNT 与其他聚合函数(如 SUM、AVG)的重要区别——空表时 COUNT 返回 0,而其他聚合函数返回 NULL。

COUNT(*) 的性能问题

InnoDB 的 COUNT(*) 是慢操作

  • MyISAM:COUNT(*) 不带 WHERE 时,直接返回表的元数据,速度极快
  • InnoDB:即使不带 WHERE,也需要遍历聚簇索引统计行数(因为 InnoDB 支持事务,不同事务看到的行数不同,无法缓存精确值)

优化方案

-- 方案1:用 SHOW TABLE STATUS 估算(不准确)
SHOW TABLE STATUS LIKE 'employees';
-- 查看 Rows 字段(估算值)

-- 方案2:维护计数器表(精确但需额外维护)
UPDATE counter_table SET total = total + 1 WHERE table_name = 'employees';

-- 方案3:用二级索引(比聚簇索引小,扫描更快)
SELECT COUNT(emp_id) FROM employees;  -- emp_id 有索引时可能比 COUNT(*) 快

结果解读:

  • SHOW TABLE STATUS 的 Rows 字段是基于索引统计的估算值,不是精确值,但获取速度极快
  • 计数器表需要应用层在插入/删除时维护,适合对实时性要求不高的场景
  • COUNT(emp_id) 如果 emp_id 列有二级索引,可能扫描更小的索引树,比扫描聚簇索引的 COUNT(*) 更快

COUNT(*) vs COUNT(1) vs COUNT(列)

写法统计对象性能(InnoDB)
COUNT(*)所有行遍历聚簇索引
COUNT(1)所有行与 COUNT(*) 相同,优化器会统一处理
COUNT(列)列非 NULL 的行可能走该列的二级索引,比聚簇索引小
COUNT(PK)主键非 NULL 的行等同于 COUNT(*),主键不会为 NULL

面试结论:在 MySQL 5.7 中,COUNT(*) 和 COUNT(1) 没有性能差异,优化器会同等对待。COUNT(列) 如果列上有二级索引,可能扫描更小的索引树。

常见误区

误区正解
COUNT(1) 比 COUNT(*) 快MySQL 5.7 优化器会同等优化,无差异。
COUNT(*) 会统计所有列不会。COUNT(*) 是特殊优化路径,不展开列。
COUNT 可以对多个列使用COUNT(列1, 列2) 在 MySQL 5.7 中语法不合法,应分别计数或用子查询。
COUNT 总是返回非 NULL空表时 COUNT 返回 0,不是 NULL。

面试考点

Q:COUNT(*)、COUNT(1)、COUNT(emp_id) 哪个快?

COUNT(*) 和 COUNT(1) 无差异。COUNT(emp_id) 如果 emp_id 是主键,等同于 COUNT(*);如果 emp_id 是二级索引列,可能扫描更小的索引树。实际差异微乎其微,应关注查询条件和索引设计。

Q:为什么 InnoDB 的 COUNT(*) 比 MyISAM 慢?

MyISAM 将总行数存储在表元数据中(因为不支持事务,行数唯一);InnoDB 支持 MVCC,不同事务看到不同的快照,必须实时遍历索引统计,无法缓存精确值。

Q:如何快速获取 InnoDB 表的近似行数?

SHOW TABLE STATUS 或查询 information_schema.tables 的 table_rows 字段。这是基于索引统计的估算值,不是精确值,但获取速度极快。

Q:COUNT(DISTINCT 列1, 列2) 合法吗?

在 MySQL 5.7 中合法,表示对列1和列2的组合去重后计数。例如 (dept, score) 的组合去重。

小结

  • COUNT(*) 统计所有行,COUNT(列) 统计非 NULL 行
  • COUNT(*) 和 COUNT(1) 在 MySQL 5.7 中无性能差异
  • InnoDB 的 COUNT(*) 需要遍历索引,大数据量时较慢
  • CASE WHEN + COUNT 是单查询条件统计的利器
  • 空表时 COUNT 返回 0,其他聚合函数返回 NULL
  • COUNT(DISTINCT 列) 用于去重计数

下一章引子:聚合函数配合 GROUP BY,就能实现"分组统计"——这是数据分析中最常用的 SQL 模式之一。

上一页
LIMIT
下一页
聚合函数