Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > MySQL数据统计

从入门到高阶实战详解如何使用MySQL做数据统计

作者:detayun

这篇文章主要为大家详细介绍了MySQL中统计分析的核心功能,包括基础聚合函数,分组统计,窗口函数(8.0+以及性能优化策略,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下

摘要:数据统计是后端开发和数据分析的高频需求。是用代码在内存里算,还是让数据库算?本文深入讲解 MySQL 统计分析的核心能力,涵盖聚合函数、分组汇总、去重计数、窗口函数以及性能优化策略,帮你搞定 90% 的数据统计场景。

一、 前言:别把计算压力扔给应用层

很多初级开发在做统计时,习惯把数据全查出来,在 Java/Python/Go 代码里循环计算:

# ❌ 错误示范:把 100 万行数据拉到内存里算
rows = db.query("SELECT * FROM orders WHERE status='paid'")
total = 0
for row in rows:
    total += row.amount

这种做法不仅慢,还会把应用服务器的内存打爆。MySQL 内置了强大的统计函数,让数据在磁盘端就完成计算,只返回结果集,这才是正确姿势。

二、 基础篇:核心聚合函数

这是统计的基石,必须熟练掌握。

1. COUNT 系列:数数的艺术

坑点SELECT COUNT(*) FROM table 在 InnoDB 中其实很快(不像 MyISAM 那样存了元数据,但 InnoDB 会走最小的二级索引),千万别为了加速而用 COUNT(1),优化器会自动把它们变成一样的执行计划。

2. SUM / AVG / MAX / MIN

最基础的求和与极值。

-- 统计近 7 天的总销售额和平均客单价
SELECT 
    SUM(amount) as total_sales,
    AVG(amount) as avg_price,
    MAX(create_time) as last_order_time
FROM orders 
WHERE create_time > NOW() - INTERVAL 7 DAY;

三、 进阶篇:分组与多维分析

单看总数没意义,我们需要按维度拆解。

1. GROUP BY:按维度切分

-- 按城市统计用户数
SELECT city, COUNT(*) 
FROM users 
GROUP BY city;

优化技巧GROUP BY 的字段必须建立索引,否则会产生临时表(Using temporary)和文件排序(Using filesort),性能极差。

2. WITH ROLLUP:小计与总计

如果你需要“各城市小计 + 全国总计”,不需要写两条 SQL,用 WITH ROLLUP 一把梭:

SELECT city, COUNT(*) 
FROM users 
GROUP BY city WITH ROLLUP;

结果会多一行 NULL,这就是总计。

3. GROUPING SETS:多维度交叉

假设你要同时看“按性别统计”和“按年龄统计”,传统写法要 UNION ALL,现在可以用:

SELECT gender, age_group, COUNT(*)
FROM users
GROUP BY GROUPING SETS ( (gender), (age_group) );

四、 高阶篇:窗口函数(MySQL 8.0+)

这是现代 SQL 的大杀器,能解决“排名”、“累计”、“移动平均”等难题,无需自连接

1. 排名问题:谁是 Top N?

-- 查询每个部门工资前 3 名的员工(允许并列)
SELECT name, department, salary,
       DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) as ranking
FROM employees;

2. 累计与移动平均

-- 计算每日销售额及近 3 天移动平均(MA3)
SELECT date, sales,
       AVG(sales) OVER (
           ORDER BY date 
           ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
       ) as moving_avg_3d
FROM daily_report;

3. 同比/环比

利用 LAG() 函数取上一行的值:

SELECT date, sales,
       LAG(sales, 1) OVER (ORDER BY date) as prev_day_sales,
       (sales - LAG(sales, 1) OVER (ORDER BY date)) / LAG(sales, 1) OVER (ORDER BY date) * 100 as growth_rate
FROM daily_report;

五、 实战篇:时间序列统计

做报表时,最怕数据“断档”。比如统计最近 30 天的日活,如果某天没数据,SQL 不会返回 0,而是直接跳过这一天。

解决方案:补全时间轴

MySQL 本身没有生成序列的函数(不像 PostgreSQL 的 generate_series),我们需要用“数字表”或者递归 CTE 来制造一个连续的时间序列,然后左连接业务表。

-- 1. 生成连续日期(递归 CTE,MySQL 8.0+)
WITH RECURSIVE date_series AS (
    SELECT CURDATE() - INTERVAL 29 DAY as dt
    UNION ALL
    SELECT dt + INTERVAL 1 DAY FROM date_series WHERE dt < CURDATE()
)
-- 2. 左连接业务表,用 IFNULL 补 0
SELECT 
    d.dt,
    COUNT(o.id) as order_count, -- 这里统计的是左连接后的非空行
    IFNULL(COUNT(o.id), 0) as real_count -- 更严谨的写法其实是 SUM(CASE WHEN o.id IS NOT NULL THEN 1 ELSE 0 END)
FROM date_series d
LEFT JOIN orders o ON DATE(o.create_time) = d.dt
GROUP BY d.dt
ORDER BY d.dt;

注:如果是低版本 MySQL,需要建一张辅助的数字表(Numbers Table)。

六、 性能优化:让统计飞起来

统计查询通常涉及全表扫描或大范围扫描,如何优化?

1.覆盖索引(Covering Index)

如果统计只涉及索引列,MySQL 就不需要回表查数据行。

-- 假设有索引 idx_city (city)
SELECT city, COUNT(*) FROM users GROUP BY city; -- 极快,只扫索引

2.避免 SELECT

统计时只查需要的字段,减少网络传输和内存开销。

3.近似计算

如果不需要 100% 精确(如 UV 统计),用 APPROX_DISTINCT(基于 HyperLogLog 算法)比 COUNT(DISTINCT) 快 10 倍以上,且内存占用极小。

SELECT APPROX_DISTINCT(user_id) FROM logs;

4.分表/分库/OLAP

如果单表数据量过亿,统计压力大,考虑分库分表。

如果统计逻辑极其复杂(多表关联、Ad-hoc 查询),不要死磕 MySQL。将数据同步到 ClickHouse、Doris 或 TiDB 这类 OLAP 数据库,查询速度能提升 10-100 倍。

七、 总结:统计函数速查表

需求场景推荐函数/语法备注
简单计数/求和COUNT, SUM, AVG基础中的基础
分组统计GROUP BY必须带索引
多级汇总WITH ROLLUP替代多次查询
排名/TopNRANK(), DENSE_RANK()窗口函数
累计/移动平均SUM() OVER (... ROWS BETWEEN)窗口函数
同比/环比LAG(), LEAD()窗口函数
近似去重APPROX_DISTINCT大数据量首选
时间轴补全递归 CTE + Left Join解决数据断层

最后的一句话:MySQL 不仅是一个 OLTP(事务处理)数据库,掌握好上述统计技巧,它也能胜任轻量级的 OLAP(分析)工作。但如果你的业务是“双十一大屏实时监控”或者“每天跑几百个复杂报表”,请果断上 ClickHouse。

到此这篇关于从入门到高阶实战详解如何使用MySQL做数据统计的文章就介绍到这了,更多相关MySQL数据统计内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文