必须显式指定order by,否则累计结果不可靠;窗口函数不会自动按业务逻辑排序,需用order by create_time, id等唯一字段保证稳定顺序,并配合partition by实现分组累计。

SUM OVER需要明确排序,否则累计结果不可靠
窗口函数 SUM() OVER() 不会自动按业务逻辑排序,比如时间先后或流水序号。如果没写 ORDER BY 子句,数据库可能按物理存储顺序累加,结果每次查询都可能不同——尤其在并发写入或表重组后。
实操建议:
- 必须在
OVER()里用ORDER BY指定唯一、稳定的排序字段,例如ORDER BY create_time, id - 避免只用
ORDER BY amount这类可能重复的字段,会导致相同金额的行排序不确定 - 若原始数据无时间戳或序号,先用
ROW_NUMBER() OVER (ORDER BY ...)生成辅助序号再累计
分区(PARTITION BY)决定累计范围
不加 PARTITION BY 就是全表累计;加了就按指定字段分组各自累计。常见需求是“每个用户各自的累计消费”,这时漏掉 PARTITION BY user_id 会导致张三的金额里混入李四的记录。
典型错误现象:SUM(amount) OVER (ORDER BY create_time) 返回的累计值远超单个用户的总和,说明没分区。
使用场景示例:
- 按月累计:用
PARTITION BY YEAR(create_time), MONTH(create_time) - 按客户累计:用
PARTITION BY customer_id - 跨客户但按产品线累计:用
PARTITION BY product_line
ORDER BY 后默认 RANGE UNBOUNDED PRECEDING,别乱改
多数人写 SUM(amount) OVER (ORDER BY create_time) 其实等价于 SUM(amount) OVER (ORDER BY create_time RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)。这个默认行为是“从第一行到当前行”求和。
容易踩的坑:
- 误写成
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW—— 看似一样,但当create_time有重复时,RANGE会把同时间所有行一起算进当前累计,ROWS只按物理顺序取到当前行,结果不同 - 手写
RANGE BETWEEN ...却用了非数值/日期列做ORDER BY,PostgreSQL 会报错,MySQL 8.0+ 会静默退化为ROWS - 想排除当前行(即“截至上一行”的累计),得显式写
RANGE BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING,但需确保排序字段无重复,否则可能跳过整组
性能注意:大表上 ORDER BY 字段必须有索引
SUM() OVER (ORDER BY x) 的执行代价直接受 x 字段索引影响。没索引时,数据库要先排序整个结果集,IO 和内存开销陡增。
验证方法:
- 执行
EXPLAIN查看是否出现WindowAgg节点配合Sort,且Sort的 cost 很高 - 对高频累计查询的字段(如
create_time、order_id)建联合索引,例如INDEX (user_id, create_time)同时支撑分区和排序 - MySQL 8.0 中,若
ORDER BY字段是主键前缀,优化器可能跳过额外排序;PostgreSQL 则更依赖显式索引
ORDER BY create_time,只要同一秒插入多笔,累计值就可能因执行计划变化而抖动。真要稳定,要么加 id 作为第二排序键,要么用带毫秒的时间戳字段。











