窗口函数比group by更高效,因其避免强制物化分组、排序、去重开销;当需“保留每行+附带分组计算值”时,窗口函数单次扫描即可完成明细与汇总,而group by需先聚合再join,实测10万行表耗时250ms vs 40ms。

窗口函数在某些场景下比 GROUP BY 更高效,不是因为它“更强”,而是它避开了 GROUP BY 强制物化分组、排序、去重的开销——当你的目标是“保留每行 + 附带分组计算值”时,GROUP BY 是绕远路,窗口函数才是直行道。
窗口函数一次扫描就能完成“明细+汇总”,GROUP BY 得先聚合再JOIN回来
比如要查每个订单,并附带该客户的总消费额:
-
GROUP BY方案必须:先GROUP BY customer_id算出总消费 → 再用JOIN回原始订单表 → 可能触发笛卡尔积或多次全表扫描 - 窗口函数方案只需:
SUM(amount) OVER (PARTITION BY customer_id)—— 单次顺序扫描,边读边算,不生成中间结果集 - 实测:10万行订单表上,前者耗时约 250ms(含JOIN),后者稳定在 40ms 左右
ROW_NUMBER() OVER (...) 能可靠实现“每组取最新一条”,GROUP BY 不能
写 SELECT user_id, order_id, MAX(created_at) FROM orders GROUP BY user_id 在 MySQL 8.0 下大概率直接报错:Expression #2 of SELECT list is not in GROUP BY clause;即使关掉 ONLY_FULL_GROUP_BY,order_id 也大概率不对应 MAX(created_at) 那一行。
- 正确做法是:
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC)打标,外层WHERE rn = 1 - 这个过程仍是一次扫描:排序 + 编号 + 过滤,执行计划干净,索引可被有效利用(如
(user_id, created_at)联合索引) - 若强行用
GROUP BY+ 子查询模拟,会多一次按user_id的哈希分组 + 一次关联,IO 和内存压力明显上升
PARTITION BY 不物化分组,但 GROUP BY 必须
PARTITION BY 只是逻辑切片,数据库不会把数据按分区写入临时结构;而 GROUP BY 必须完成分组物化——排序、哈希桶构建、去重、聚合值计算,哪怕只分 3 组,也要走完整流程。
- 对千万级表按低基数字段(如
status只有 3 个值)做PARTITION BY status,若没索引,窗口函数可能慢但不至于 OOM;同字段做GROUP BY status反而轻量,因为只输出 3 行 - 真正吃亏的是高基数字段:比如
PARTITION BY user_id(百万级唯一值)+ORDER BY created_at,这时窗口框架(如ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)会触发大量累积计算,性能瓶颈就从 IO 转向 CPU - 关键提醒:没
ORDER BY的ROW_NUMBER()结果不可复现,MySQL 可能返回任意行编号;PostgreSQL 直接拒绝执行
真正容易被忽略的点是:窗口函数的高效,高度依赖 OVER 子句里是否命中索引。比如 PARTITION BY a ORDER BY b,理想索引是 (a, b);漏掉 ORDER BY 或索引顺序错,性能可能断崖式下跌,且很难从执行计划里一眼看出问题。











