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

📅 发布时间:2026/7/16 0:23:01 👁️ 浏览次数:
如何使用 MySQL 做数据统计:从入门到高阶实战
摘要数据统计是后端开发和数据分析的高频需求。是用代码在内存里算还是让数据库算本文深入讲解 MySQL 统计分析的核心能力涵盖聚合函数、分组汇总、去重计数、窗口函数以及性能优化策略帮你搞定 90% 的数据统计场景。一、 前言别把计算压力扔给应用层很多初级开发在做统计时习惯把数据全查出来在 Java/Python/Go 代码里循环计算# ❌ 错误示范把 100 万行数据拉到内存里算rowsdb.query(SELECT * FROM orders WHERE statuspaid)total0forrowinrows:totalrow.amount这种做法不仅慢还会把应用服务器的内存打爆。MySQL 内置了强大的统计函数让数据在磁盘端就完成计算只返回结果集这才是正确姿势。二、 基础篇核心聚合函数这是统计的基石必须熟练掌握。1. COUNT 系列数数的艺术COUNT(*)统计行数不关心 NULL性能最优。COUNT(field)统计指定字段非 NULL 的行数。COUNT(DISTINCT field)统计去重后的数量。⚠️ 坑点SELECT COUNT(*) FROM table在 InnoDB 中其实很快不像 MyISAM 那样存了元数据但 InnoDB 会走最小的二级索引千万别为了加速而用COUNT(1)优化器会自动把它们变成一样的执行计划。2. SUM / AVG / MAX / MIN最基础的求和与极值。-- 统计近 7 天的总销售额和平均客单价SELECTSUM(amount)astotal_sales,AVG(amount)asavg_price,MAX(create_time)aslast_order_timeFROMordersWHEREcreate_timeNOW()-INTERVAL7DAY;三、 进阶篇分组与多维分析单看总数没意义我们需要按维度拆解。1. GROUP BY按维度切分-- 按城市统计用户数SELECTcity,COUNT(*)FROMusersGROUPBYcity;优化技巧GROUP BY的字段必须建立索引否则会产生临时表Using temporary和文件排序Using filesort性能极差。2. WITH ROLLUP小计与总计如果你需要“各城市小计 全国总计”不需要写两条 SQL用WITH ROLLUP一把梭SELECTcity,COUNT(*)FROMusersGROUPBYcityWITH ROLLUP;结果会多一行NULL这就是总计。3. GROUPING SETS多维度交叉假设你要同时看“按性别统计”和“按年龄统计”传统写法要UNION ALL现在可以用SELECTgender,age_group,COUNT(*)FROMusersGROUPBYGROUPING SETS((gender),(age_group));四、 高阶篇窗口函数MySQL 8.0这是现代 SQL 的大杀器能解决“排名”、“累计”、“移动平均”等难题无需自连接。1. 排名问题谁是 Top N-- 查询每个部门工资前 3 名的员工允许并列SELECTname,department,salary,DENSE_RANK()OVER(PARTITIONBYdepartmentORDERBYsalaryDESC)asrankingFROMemployees;ROW_NUMBER()不重复排名 (1, 2, 3)RANK()跳跃排名 (1, 1, 3)DENSE_RANK()连续排名 (1, 1, 2)2. 累计与移动平均-- 计算每日销售额及近 3 天移动平均MA3SELECTdate,sales,AVG(sales)OVER(ORDERBYdateROWSBETWEEN2PRECEDINGANDCURRENTROW)asmoving_avg_3dFROMdaily_report;3. 同比/环比利用LAG()函数取上一行的值SELECTdate,sales,LAG(sales,1)OVER(ORDERBYdate)asprev_day_sales,(sales-LAG(sales,1)OVER(ORDERBYdate))/LAG(sales,1)OVER(ORDERBYdate)*100asgrowth_rateFROMdaily_report;五、 实战篇时间序列统计做报表时最怕数据“断档”。比如统计最近 30 天的日活如果某天没数据SQL 不会返回 0而是直接跳过这一天。解决方案补全时间轴MySQL 本身没有生成序列的函数不像 PostgreSQL 的generate_series我们需要用“数字表”或者递归 CTE 来制造一个连续的时间序列然后左连接业务表。-- 1. 生成连续日期递归 CTEMySQL 8.0WITHRECURSIVE date_seriesAS(SELECTCURDATE()-INTERVAL29DAYasdtUNIONALLSELECTdtINTERVAL1DAYFROMdate_seriesWHEREdtCURDATE())-- 2. 左连接业务表用 IFNULL 补 0SELECTd.dt,COUNT(o.id)asorder_count,-- 这里统计的是左连接后的非空行IFNULL(COUNT(o.id),0)asreal_count-- 更严谨的写法其实是 SUM(CASE WHEN o.id IS NOT NULL THEN 1 ELSE 0 END)FROMdate_series dLEFTJOINorders oONDATE(o.create_time)d.dtGROUPBYd.dtORDERBYd.dt;注如果是低版本 MySQL需要建一张辅助的数字表Numbers Table。六、 性能优化让统计飞起来统计查询通常涉及全表扫描或大范围扫描如何优化覆盖索引Covering Index如果统计只涉及索引列MySQL 就不需要回表查数据行。-- 假设有索引 idx_city (city)SELECTcity,COUNT(*)FROMusersGROUPBYcity;-- 极快只扫索引**避免 SELECT ***统计时只查需要的字段减少网络传输和内存开销。近似计算如果不需要 100% 精确如 UV 统计用APPROX_DISTINCT基于 HyperLogLog 算法比COUNT(DISTINCT)快 10 倍以上且内存占用极小。SELECTAPPROX_DISTINCT(user_id)FROMlogs;分表/分库/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。如果你觉得这篇文章有帮助欢迎点赞、收藏并分享给你的开发伙伴