MySQL 8.0+ 原生支持 SUM() OVER(ORDER BY ...) 实现可靠累计求和,必须显式指定确定性 ORDER BY 字段,可选 PARTITION BY 划分独立窗口,且排序字段建议建索引以提升性能。

MySQL 8.0 窗口函数支持累计求和,无需自连接或变量 hack
MySQL 8.0+ 原生支持 SUM() 窗口函数,这是实现累计求和最可靠的方式。早于 8.0 的版本靠 @var := @var + value 或自连接模拟,但存在执行顺序不可靠、并发不安全等问题。8.0 后直接用 SUM() OVER() 即可,语义清晰、结果确定。
正确写法:ORDER BY 必须显式指定,否则累计无意义
窗口函数的累计行为完全依赖 ORDER BY 子句——它定义“累计到哪一行为止”。漏写或写错排序字段,会导致结果随机或全量求和(等价于 SUM() OVER() 无 ORDER BY)。
常见错误示例:
SELECT id, amount, SUM(amount) OVER(ORDER BY id) AS running_total FROM sales;
这没问题;但若写成:
SELECT id, amount, SUM(amount) OVER() AS running_total FROM sales;
→ 得到的是每行都显示总和,不是累计值。
-
ORDER BY字段必须是确定性排序依据(如主键、时间戳),避免用可能重复的字段(如status)单独排序 - 若需按多列排序(如先按
date再按id),写成ORDER BY date, id - 升序(
ASC)是默认行为;降序(DESC)会从后往前累计,注意业务含义是否合理
分区(PARTITION BY)控制累计范围,别误用成 GROUP BY
当需要“每个用户各自的累计”或“每月独立累计”时,用 PARTITION BY 划分窗口,而非 GROUP BY。后者会聚合掉明细行,而窗口函数保留原始行数。
例如按用户计算每日消费累计:
SELECT user_id, order_date, amount,
SUM(amount) OVER(PARTITION BY user_id ORDER BY order_date) AS user_running_total
FROM orders;
-
PARTITION BY user_id表示每个user_id独立开一个窗口,互不影响 - 不能把
PARTITION BY和GROUP BY混用在同一查询中,除非你明确需要先分组再窗口计算(极少见) - 分区字段值为
NULL的行会被归入同一组,如有意排除,加WHERE user_id IS NOT NULL
性能注意:ORDER BY 字段最好有索引,尤其大数据量时
MySQL 会为窗口函数的 ORDER BY 执行一次排序。如果 ORDER BY 字段没索引,且数据量大(比如百万级订单表),查询可能明显变慢,甚至触发磁盘临时表。
- 检查执行计划:
EXPLAIN中看Extra是否含Using filesort - 推荐在常用窗口排序字段上建联合索引,例如:
CREATE INDEX idx_user_date ON orders(user_id, order_date); - 避免在表达式上排序,如
ORDER BY DATE(order_date)—— 无法走索引
累计求和本身不复杂,但窗口定义的边界(ORDER BY、PARTITION BY)和底层排序效率,才是实际项目里最容易出问题的地方。


















