mysql 8.0累计求和必须用sum() over(),且over中order by不可省略;需配合partition by实现分组累加,默认帧为rows between unbounded preceding and current row。

MySQL 8.0 窗口函数累计求和必须用 SUM() OVER()
MySQL 8.0+ 才支持窗口函数,SUM() OVER() 是唯一能做“按顺序累加”的标准方式。别试图用自连接或变量(@sum := @sum + amount)模拟——在排序不稳定、并发查询或优化器重排时结果不可靠,且 MySQL 8.0 已明确不保证用户变量在窗口场景下的行为。
ORDER BY 子句在 OVER() 中不可省略
累计求和本质依赖行序,OVER(ORDER BY date) 不仅是语法要求,更是语义前提。漏写会报错:ERROR 3589 (HY000): Window '<unnamed_window>' with no ORDER BY and frame specification is not allowed for aggregate functions</unnamed_window>。
实操建议:
- 确保
ORDER BY字段有索引(如INDEX (user_id, created_at)),否则大表上OVER(ORDER BY created_at)可能触发 filesort,拖慢查询 - 若需分组内累计(如每个用户独立累加),必须配合
PARTITION BY:OVER(PARTITION BY user_id ORDER BY created_at) - 避免用表达式或函数作为
ORDER BY字段(如ORDER BY DATE(created_at)),会导致无法利用索引
累计求和默认是 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
这是 MySQL 的隐式帧定义,等价于“从分区开头到当前行”。多数场景下无需显式写,但要注意:
- 如果误写成
RANGE BETWEEN ...,且ORDER BY字段存在重复值(如多个订单同秒创建),RANGE会把同值所有行视为“同一位置”,导致累计值跳跃 - 想跳过当前行只累加前面的(如“截至上一行的累计”),得显式写:
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING -
ROWS按物理行序,RANGE按逻辑值范围——除非业务真需要按值聚合(如按金额区间分段累计),否则一律用ROWS
性能敏感时优先考虑覆盖索引 + WHERE 下推
窗口函数本身无法使用索引加速计算,但它的输入结果集可以被前置条件过滤。例如:
SELECT id, amount, SUM(amount) OVER(PARTITION BY user_id ORDER BY created_at) AS cumsum FROM orders WHERE created_at >= '2024-01-01';
这个 WHERE 能让 MySQL 先走 created_at 索引过滤数据,再对小结果集跑窗口函数。如果把条件挪到外层 HAVING 或子查询里,就可能全表扫描。
另一个容易被忽略的点:如果只需要最新 N 条的累计值(比如最近 30 天的每日流水累计),别在窗口函数后用 LIMIT——它会在完整计算完所有行后再截断。应该先用子查询或 CTE 限定时间范围,再套窗口。











