MySQL中五大基础聚合函数的区别与应用

 更新时间:2026年06月24日 09:13:45   作者:花生了什么事o  
本文系统介绍了 MySQL 五大聚合函数(COUNT、SUM、AVG、MAX、MIN)的用法,重点厘清了 COUNT(*)、COUNT(1)、COUNT(列名)三者的区别,并解析了 GROUP BY 分组机制与 WHERE/HAVING 的过滤时机差异,有需要的小伙伴可以了解下

聚合函数是什么

聚合函数是对一组行做计算,返回单个值的函数。

MySQL 提供了以下五个最常用的聚合函数:

函数作用
COUNT()统计行数
SUM()求和
AVG()求平均值
MAX()求最大值
MIN()求最小值

这五个函数就足以覆盖日常开发中 90% 以上的统计需求。下面逐个拆开来讲。

五大基础聚合函数

COUNT:数行数

COUNT() 用来统计满足条件的行数。它有三种常见写法,面试和日常开发中经常被混为一谈:

SELECT COUNT(*) FROM orders;
SELECT COUNT(1) FROM orders;
SELECT COUNT(status) FROM orders;

这三种写法看起来差不多,实际行为有明确的区别。

COUNT(*) vs COUNT(1)

先说结论:COUNT(*)COUNT(1) 在 MySQL 中完全等价,性能没有任何区别。

COUNT(*)* 不是"取出所有列"的意思,它只是一个语法符号,表示"统计行数"。COUNT(1) 中的 1 也不是取第一列,而是对每一行计算常量表达式 1,然后计数。MySQL 的查询优化器会把两者统一处理,生成完全相同的执行计划。

你可以用 EXPLAIN 验证:

EXPLAIN SELECT COUNT(*) FROM orders;
EXPLAIN SELECT COUNT(1) FROM orders;
-- 两条语句的 Extra、rows、key 完全一致

所以不用纠结用哪个,选 COUNT(*) 就好,它是 SQL 标准写法,可读性也最好。

COUNT(列名) 的行为

COUNT(列名) 和前两个有本质区别:它只统计该列不为 NULL 的行。

-- 假设 orders 表有 100 行,其中 status 列有 5 个 NULL
SELECT COUNT(*) FROM orders;          -- 结果:100
SELECT COUNT(1) FROM orders;          -- 结果:100
SELECT COUNT(status) FROM orders;     -- 结果:95

COUNT(status) 遍历每一行时,会检查 status 是否为 NULL,是就跳过。这意味着 COUNT(列名) 天然会排除 NULL 行,在统计"有多少行这个字段有值"时很有用,但如果本意是统计总行数,用它就会少数。

三者对比

写法统计范围NULL 处理性能
COUNT(*)总行数不受 NULL 影响最优(MySQL 优化)
COUNT(1)总行数不受 NULL 影响COUNT(*) 一样
COUNT(列名)该列非 NULL 的行数跳过 NULL一样(但语义不同)

总结:统计总行数用 COUNT(*)COUNT(1),统计某列有值的行数用 COUNT(列名)

COUNT(DISTINCT 列名)

还有一个常见写法,用来统计去重后的行数:

-- 有多少个不同的用户下过单
SELECT COUNT(DISTINCT user_id) FROM orders;

COUNT(DISTINCT 列名) 先去重再计数,NULL 不参与——即使有多行 NULL,也只算一个(实际上 MySQL 的 DISTINCT 会直接跳过 NULL)。

性能提醒

COUNT(*) 在 InnoDB 中需要遍历索引来计数,大表上执行会比较慢。如果只是想要一个近似值,可以查 information_schema.TABLES 里的 TABLE_ROWS 字段,那是 InnoDB 的估算值,速度极快但不精确。

-- 快速获取近似行数
SELECT TABLE_ROWS
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'orders';

SUM 和 AVG:求和与平均

SUM() 对数值列求和,AVG() 求平均值。用法很直观:

-- 所有订单的总金额
SELECT SUM(amount) FROM orders;

-- 所有订单的平均金额
SELECT AVG(amount) FROM orders;

有一个细节容易忽略:AVG() 的计算公式是 SUM() / COUNT(列名),而不是 SUM() / COUNT(*)。 如果列里有 NULL,NULL 不参与分子也不参与分母。

-- 假设 amount 列有值:100, 200, NULL, 300
-- SUM(amount) = 600(NULL 被忽略)
-- AVG(amount) = 600 / 3 = 200(不是 600 / 4 = 150)

如果你期望 NULL 当作 0 来参与计算,需要用 IFNULLCOALESCE

SELECT AVG(IFNULL(amount, 0)) FROM orders;
-- 现在 NULL 被当作 0,结果是 600 / 4 = 150

MAX 和 MIN:极值查询

MAX()MIN() 分别返回一组数据中的最大值和最小值:

-- 最大的订单金额
SELECT MAX(amount) FROM orders;

-- 最早的订单时间
SELECT MIN(create_time) FROM orders;

它们不仅适用于数值,也适用于字符串和日期。字符串比较的是字典序,日期比较的是时间先后。

一个常见误区: 很多人以为 MAXMIN 只能用在 SELECT 里,其实它们在子查询中更有价值:

-- 找出金额最大的那条订单的完整信息
SELECT * FROM orders
WHERE amount = (SELECT MAX(amount) FROM orders);

不过这种写法有一个隐患:如果有多条订单金额相同且都是最大值,会返回多行。如果你只想要一条,加 LIMIT 1

GROUP BY:把数据切成一块一块再聚合

单个聚合函数只能算出"全局汇总"。但实际业务中,我们更常需要"分组统计"——比如每个用户的订单数、每个月的销售额、每个状态的订单量。

这就是 GROUP BY 的作用:按某个列(或多个列)把数据分成若干组,每组独立做聚合。

-- 每个用户的订单数
SELECT user_id, COUNT(*) AS order_count
FROM orders
GROUP BY user_id;

-- 每个月的销售总额
SELECT
    DATE_FORMAT(create_time, '%Y-%m') AS month,
    SUM(amount) AS monthly_total
FROM orders
GROUP BY DATE_FORMAT(create_time, '%Y-%m');

执行逻辑可以用一句话概括:先分组,再聚合,每组出一行结果。

SELECT 中出现的非聚合列,必须出现在 GROUP BY 中。 这是 SQL 的基本规则。比如下面这条就是错误的:

-- 错误:status 没有出现在 GROUP BY 中,也不是聚合函数的参数
SELECT user_id, status, COUNT(*)
FROM orders
GROUP BY user_id;

-- 正确:status 也加入分组
SELECT user_id, status, COUNT(*)
FROM orders
GROUP BY user_id, status;

MySQL 在 ONLY_FULL_GROUP_BY 模式下会直接拒绝这种写法。关闭这个模式虽然能执行,但结果是不确定的:status 取的是每组中哪一行的值,MySQL 自己也说不清。

WHERE vs HAVING:过滤时机不一样

分组之后,你可能想对结果做进一步筛选。比如"只看订单数超过 10 的用户"。这时候有两个选择:WHEREHAVING

它们的核心区别在于过滤时机:

WHEREHAVING
过滤时机分组之前分组之后
能否用聚合函数不能
操作对象原始行分组后的结果集
-- WHERE:在分组前过滤原始行
SELECT user_id, COUNT(*) AS order_count
FROM orders
WHERE status = 'paid'          -- 先过滤掉未支付的订单
GROUP BY user_id;

-- HAVING:在分组后过滤聚合结果
SELECT user_id, COUNT(*) AS order_count
FROM orders
GROUP BY user_id
HAVING COUNT(*) > 10;           -- 再过滤掉订单数不超过 10 的组

如果你同时需要 WHEREHAVING,顺序是固定的:WHERE 在前,GROUP BY 在中,HAVING 在后。

-- 完整的执行顺序:WHERE → GROUP BY → HAVING → ORDER BY → LIMIT
SELECT user_id, SUM(amount) AS total
FROM orders
WHERE status = 'paid'           -- 第一步:过滤原始行
GROUP BY user_id                -- 第二步:分组
HAVING total > 1000             -- 第三步:过滤分组结果
ORDER BY total DESC             -- 第四步:排序
LIMIT 10;                       -- 第五步:取前 10 条

一个常见的错误:WHERE 里使用聚合函数。

-- 错误:WHERE 还没到分组阶段,不能用聚合函数
SELECT user_id, COUNT(*)
FROM orders
WHERE COUNT(*) > 10
GROUP BY user_id;

-- 正确:用 HAVING
SELECT user_id, COUNT(*)
FROM orders
GROUP BY user_id
HAVING COUNT(*) > 10;

逻辑上也说得通:WHERE 是逐行过滤,COUNT(*) 是一组行的统计结果,逐行阶段根本不知道"这一组有多少行",所以没法用。

聚合函数的嵌套与子查询

聚合函数可以嵌套使用,但有一条铁律:聚合函数不能嵌套在 WHERE 子句中直接使用,必须放在子查询里。

-- 错误:MySQL 不允许在 WHERE 中直接用聚合函数
SELECT * FROM orders WHERE amount > AVG(amount);

-- 正确:用子查询包一层
SELECT * FROM orders
WHERE amount > (SELECT AVG(amount) FROM orders);

同样,聚合函数可以和 GROUP BY 结合后作为子查询使用:

-- 找出每个用户消费最高的那笔订单
SELECT o.*
FROM orders o
INNER JOIN (
    SELECT user_id, MAX(amount) AS max_amount
    FROM orders
    GROUP BY user_id
) t ON o.user_id = t.user_id AND o.amount = t.max_amount;

这种"先聚合再关联"的模式在报表统计中非常常见。先在子查询中算出每组的聚合值,再和原表 JOIN 取出完整行。

小结

聚合函数的本质就是把多行数据压缩成一个值。 COUNT 数行数,SUM 算总和,AVG 算平均,MAXMIN 找极值。配合 GROUP BY 可以按字段维度分组统计,配合 HAVING 可以对分组结果再过滤。

到此这篇关于MySQL中五大基础聚合函数的区别与应用的文章就介绍到这了,更多相关MySQL聚合函数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • mysql连接器之mysql-connector-java问题

    mysql连接器之mysql-connector-java问题

    这篇文章主要介绍了mysql连接器之mysql-connector-java问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • MySQL修改root密码的3种实用方法

    MySQL修改root密码的3种实用方法

    最近在看项目,搭建本地环境时候,忘记mysql的密码,怎么修改密码,网上找了半天,终于配合着几个帖子搞定了,下面这篇文章主要给大家介绍了关于MySQL修改root密码的3种实用方法,需要的朋友可以参考下
    2023-11-11
  • MySQL 编码utf8 与 utf8mb4 utf8mb4_unicode_ci 与 utf8mb4_general_ci

    MySQL 编码utf8 与 utf8mb4 utf8mb4_unicode_ci 与 utf8mb4_general_

    这篇文章主要介绍了MySQL 编码utf8 与 utf8mb4 utf8mb4_unicode_ci 与 utf8mb4_general_ci的相关知识,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-05-05
  • 详解MySQL连接挂死的原因

    详解MySQL连接挂死的原因

    本次分享的是一次关于 MySQL 高可用问题的定位过程,其中曲折颇多但问题本身却比较有些代表性,遂将其记录以供参考。
    2021-05-05
  • Linux虚拟机下mysql 5.7安装配置方法图文教程

    Linux虚拟机下mysql 5.7安装配置方法图文教程

    这篇文章主要为大家详细介绍了Linux虚拟机下mysql 5.7安装配置方法图文教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-01-01
  • MACOS中忘记MySQL root密码的解决方案

    MACOS中忘记MySQL root密码的解决方案

    这篇文章主要向大家讲述的是在MAC系统中MySQL重设root密码的实际操作步骤,在实际操作中忘记MySQL root密码时常会发生的,下面就是本教程的详细内容介绍。
    2017-03-03
  • MySQL中CRUD操作及常用查询语法举例详解

    MySQL中CRUD操作及常用查询语法举例详解

    SQL是一种标准化的语言,它允许你在数据库上执行操作,如创建项目,查询内容,更新内容,并删除条目等操作,这篇文章主要介绍了MySQL中CRUD操作及常用查询语法的相关资料,需要的朋友可以参考下
    2025-10-10
  • MySQL安全输入密码的一些操作介绍

    MySQL安全输入密码的一些操作介绍

    这篇文章主要介绍了MySQL安全输入密码的一些操作,示例基于Linux操作系统,需要的朋友可以参考下
    2015-07-07
  • MySQL隔离级别和锁机制的深入讲解

    MySQL隔离级别和锁机制的深入讲解

    这篇文章主要给大家介绍了关于MySQL隔离级别和锁机制的相关资料,主要包括深入理解MySQL隔离级别和锁机制使用实例、应用技巧、基本知识点总结和需要注意事项,需要的朋友可以参考下
    2021-08-08
  • Mysql 默认字符集设置方法(免安装版)

    Mysql 默认字符集设置方法(免安装版)

    有些时候我们在使用非安装版的mysql是需要设置默认字符集的时候,就需要这样的修改了。安装版的可以选择的。
    2009-03-03

最新评论