從入門到高階實(shí)戰(zhàn)詳解如何使用MySQL做數(shù)據(jù)統(tǒng)計(jì)
摘要:數(shù)據(jù)統(tǒng)計(jì)是后端開發(fā)和數(shù)據(jù)分析的高頻需求。是用代碼在內(nèi)存里算,還是讓數(shù)據(jù)庫算?本文深入講解 MySQL 統(tǒng)計(jì)分析的核心能力,涵蓋聚合函數(shù)、分組匯總、去重計(jì)數(shù)、窗口函數(shù)以及性能優(yōu)化策略,幫你搞定 90% 的數(shù)據(jù)統(tǒng)計(jì)場景。
一、 前言:別把計(jì)算壓力扔給應(yīng)用層
很多初級開發(fā)在做統(tǒng)計(jì)時(shí),習(xí)慣把數(shù)據(jù)全查出來,在 Java/Python/Go 代碼里循環(huán)計(jì)算:
# ? 錯(cuò)誤示范:把 100 萬行數(shù)據(jù)拉到內(nèi)存里算
rows = db.query("SELECT * FROM orders WHERE status='paid'")
total = 0
for row in rows:
total += row.amount
這種做法不僅慢,還會把應(yīng)用服務(wù)器的內(nèi)存打爆。MySQL 內(nèi)置了強(qiáng)大的統(tǒng)計(jì)函數(shù),讓數(shù)據(jù)在磁盤端就完成計(jì)算,只返回結(jié)果集,這才是正確姿勢。
二、 基礎(chǔ)篇:核心聚合函數(shù)
這是統(tǒng)計(jì)的基石,必須熟練掌握。
1. COUNT 系列:數(shù)數(shù)的藝術(shù)
COUNT(*):統(tǒng)計(jì)行數(shù)(不關(guān)心 NULL,性能最優(yōu))。COUNT(field):統(tǒng)計(jì)指定字段非 NULL 的行數(shù)。COUNT(DISTINCT field):統(tǒng)計(jì)去重后的數(shù)量。
坑點(diǎn):SELECT COUNT(*) FROM table 在 InnoDB 中其實(shí)很快(不像 MyISAM 那樣存了元數(shù)據(jù),但 InnoDB 會走最小的二級索引),千萬別為了加速而用 COUNT(1),優(yōu)化器會自動把它們變成一樣的執(zhí)行計(jì)劃。
2. SUM / AVG / MAX / MIN
最基礎(chǔ)的求和與極值。
-- 統(tǒng)計(jì)近 7 天的總銷售額和平均客單價(jià)
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;
三、 進(jìn)階篇:分組與多維分析
單看總數(shù)沒意義,我們需要按維度拆解。
1. GROUP BY:按維度切分
-- 按城市統(tǒng)計(jì)用戶數(shù) SELECT city, COUNT(*) FROM users GROUP BY city;
優(yōu)化技巧:GROUP BY 的字段必須建立索引,否則會產(chǎn)生臨時(shí)表(Using temporary)和文件排序(Using filesort),性能極差。
2. WITH ROLLUP:小計(jì)與總計(jì)
如果你需要“各城市小計(jì) + 全國總計(jì)”,不需要寫兩條 SQL,用 WITH ROLLUP 一把梭:
SELECT city, COUNT(*) FROM users GROUP BY city WITH ROLLUP;
結(jié)果會多一行 NULL,這就是總計(jì)。
3. GROUPING SETS:多維度交叉
假設(shè)你要同時(shí)看“按性別統(tǒng)計(jì)”和“按年齡統(tǒng)計(jì)”,傳統(tǒng)寫法要 UNION ALL,現(xiàn)在可以用:
SELECT gender, age_group, COUNT(*) FROM users GROUP BY GROUPING SETS ( (gender), (age_group) );
四、 高階篇:窗口函數(shù)(MySQL 8.0+)
這是現(xiàn)代 SQL 的大殺器,能解決“排名”、“累計(jì)”、“移動平均”等難題,無需自連接。
1. 排名問題:誰是 Top N?
-- 查詢每個(gè)部門工資前 3 名的員工(允許并列)
SELECT name, department, salary,
DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) as ranking
FROM employees;
ROW_NUMBER():不重復(fù)排名 (1, 2, 3)RANK():跳躍排名 (1, 1, 3)DENSE_RANK():連續(xù)排名 (1, 1, 2)
2. 累計(jì)與移動平均
-- 計(jì)算每日銷售額及近 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. 同比/環(huán)比
利用 LAG() 函數(shù)取上一行的值:
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;
五、 實(shí)戰(zhàn)篇:時(shí)間序列統(tǒng)計(jì)
做報(bào)表時(shí),最怕數(shù)據(jù)“斷檔”。比如統(tǒng)計(jì)最近 30 天的日活,如果某天沒數(shù)據(jù),SQL 不會返回 0,而是直接跳過這一天。
解決方案:補(bǔ)全時(shí)間軸
MySQL 本身沒有生成序列的函數(shù)(不像 PostgreSQL 的 generate_series),我們需要用“數(shù)字表”或者遞歸 CTE 來制造一個(gè)連續(xù)的時(shí)間序列,然后左連接業(yè)務(wù)表。
-- 1. 生成連續(xù)日期(遞歸 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. 左連接業(yè)務(wù)表,用 IFNULL 補(bǔ) 0
SELECT
d.dt,
COUNT(o.id) as order_count, -- 這里統(tǒng)計(jì)的是左連接后的非空行
IFNULL(COUNT(o.id), 0) as real_count -- 更嚴(yán)謹(jǐn)?shù)膶懛ㄆ鋵?shí)是 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,需要建一張輔助的數(shù)字表(Numbers Table)。
六、 性能優(yōu)化:讓統(tǒng)計(jì)飛起來
統(tǒng)計(jì)查詢通常涉及全表掃描或大范圍掃描,如何優(yōu)化?
1.覆蓋索引(Covering Index)
如果統(tǒng)計(jì)只涉及索引列,MySQL 就不需要回表查數(shù)據(jù)行。
-- 假設(shè)有索引 idx_city (city) SELECT city, COUNT(*) FROM users GROUP BY city; -- 極快,只掃索引
2.避免 SELECT
統(tǒng)計(jì)時(shí)只查需要的字段,減少網(wǎng)絡(luò)傳輸和內(nèi)存開銷。
3.近似計(jì)算
如果不需要 100% 精確(如 UV 統(tǒng)計(jì)),用 APPROX_DISTINCT(基于 HyperLogLog 算法)比 COUNT(DISTINCT) 快 10 倍以上,且內(nèi)存占用極小。
SELECT APPROX_DISTINCT(user_id) FROM logs;
4.分表/分庫/OLAP
如果單表數(shù)據(jù)量過億,統(tǒng)計(jì)壓力大,考慮分庫分表。
如果統(tǒng)計(jì)邏輯極其復(fù)雜(多表關(guān)聯(lián)、Ad-hoc 查詢),不要死磕 MySQL。將數(shù)據(jù)同步到 ClickHouse、Doris 或 TiDB 這類 OLAP 數(shù)據(jù)庫,查詢速度能提升 10-100 倍。
七、 總結(jié):統(tǒng)計(jì)函數(shù)速查表
| 需求場景 | 推薦函數(shù)/語法 | 備注 |
|---|---|---|
| 簡單計(jì)數(shù)/求和 | COUNT, SUM, AVG | 基礎(chǔ)中的基礎(chǔ) |
| 分組統(tǒng)計(jì) | GROUP BY | 必須帶索引 |
| 多級匯總 | WITH ROLLUP | 替代多次查詢 |
| 排名/TopN | RANK(), DENSE_RANK() | 窗口函數(shù) |
| 累計(jì)/移動平均 | SUM() OVER (... ROWS BETWEEN) | 窗口函數(shù) |
| 同比/環(huán)比 | LAG(), LEAD() | 窗口函數(shù) |
| 近似去重 | APPROX_DISTINCT | 大數(shù)據(jù)量首選 |
| 時(shí)間軸補(bǔ)全 | 遞歸 CTE + Left Join | 解決數(shù)據(jù)斷層 |
最后的一句話:MySQL 不僅是一個(gè) OLTP(事務(wù)處理)數(shù)據(jù)庫,掌握好上述統(tǒng)計(jì)技巧,它也能勝任輕量級的 OLAP(分析)工作。但如果你的業(yè)務(wù)是“雙十一大屏實(shí)時(shí)監(jiān)控”或者“每天跑幾百個(gè)復(fù)雜報(bào)表”,請果斷上 ClickHouse。
到此這篇關(guān)于從入門到高階實(shí)戰(zhàn)詳解如何使用MySQL做數(shù)據(jù)統(tǒng)計(jì)的文章就介紹到這了,更多相關(guān)MySQL數(shù)據(jù)統(tǒng)計(jì)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mysql啟動報(bào)錯(cuò)MySQL server PID file could not be found
這篇文章主要介紹了mysql啟動報(bào)錯(cuò)MySQL server PID file could not be found,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
ClickHouse使用MySQL數(shù)據(jù)庫引擎的實(shí)現(xiàn)
MySQL 數(shù)據(jù)庫引擎是 ClickHouse 提供的一種集成引擎,這不是指 ClickHouse本身使用的存儲引擎,它允許你直接在 ClickHouse 中查詢存儲在遠(yuǎn)程 MySQL 服務(wù)器上的數(shù)據(jù),下面就來詳細(xì)的介紹一下,感興趣的可以了解一下2026-01-01
Navicat連接MySQL保姆級教程(含詳細(xì)圖文)
Navicat是一款支持多種主流數(shù)據(jù)庫的圖形化管理工具,提供可視化界面簡化數(shù)據(jù)庫操作,這篇文章主要介紹了Navicat連接MySQL保姆級教程的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2026-04-04
關(guān)于MYSQL中每個(gè)用戶取1條記錄的三種寫法(group by xxx)
本篇文章是對MYSQL中每個(gè)用戶取1條記錄的三種寫法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-07-07
Mysql查詢數(shù)據(jù)庫連接狀態(tài)以及連接信息詳解
這篇文章主要給大家介紹了關(guān)于Mysql查詢數(shù)據(jù)庫連接狀態(tài)以及連接信息的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-04-04

