开窗函数中省略ORDER BY时,SUM() OVER(PARTITION BY ...)计算分组总和而非累计和;添加ORDER BY后才按行累计,且必须显式指定唯一排序以保证结果确定性。

省略 ORDER BY 时 SUM() OVER() 默认对全分区求和
不加 ORDER BY,SUM(amount) OVER (PARTITION BY user_id) 就是把每个 user_id 组内所有行的 amount 加起来,每行结果都一样。这不是累计,是分组总和——等价于 SUM(amount) OVER (PARTITION BY user_id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)。
常见错误现象:SELECT user_id, amount, SUM(amount) OVER (PARTITION BY user_id) 返回每行都带该用户全部订单总额,但你想看的是“截至当前订单的累计金额”,那就必须加 ORDER BY created_at。
加上 ORDER BY 后窗口才开始“逐行推进”
一旦写了 ORDER BY,默认帧就是 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,也就是从分区第一行加到当前行。这个行为不可跳过,也不可隐式关闭。
- 没索引的
ORDER BY字段(如无索引的created_at)会触发磁盘排序,大表可能比自关联还慢 - 如果
created_at有重复值(比如同一秒多笔订单),数据库自由决定同值行顺序,导致ROW_NUMBER()或累计和每次执行结果不一致 - 正确做法是补一个唯一字段:
ORDER BY created_at DESC, id DESC
ORDER BY 缺失时 ROW_NUMBER()、RANK() 类函数结果不可靠
ROW_NUMBER() OVER (PARTITION BY dept_name) 不报错,但编号顺序由存储物理顺序或引擎随机决定,重跑可能变样。这不是 bug,是标准允许的行为。
必须显式写 ORDER BY 才能保证确定性:
- 业务要“最新一条”,就写
ORDER BY created_at DESC, id DESC - 时间字段为
NULL时,默认排最前,可能把脏数据标成第 1 名;需要ORDER BY created_at DESC NULLS LAST(PostgreSQL/Oracle 支持,MySQL 不支持) - MySQL 8.0+ 支持
NULLS LAST语法,但旧版只能用IFNULL(created_at, '1970-01-01')临时绕过
空 OVER() 即 OVER () 会触发全表隐式排序
COUNT(*) OVER () 看似只是加个总数列,但在 PostgreSQL 中,它会让优化器启动一次全表排序流程(即使没写 ORDER BY),因为实现依赖排序缓冲区。这不是你想要的开销。
更稳的替代方案:
- 先查
SELECT COUNT(*) FROM t,再用 JOIN 或 CTE 关联回去 - 在 SQL Server 中加
OPTION (RECOMPILE)避免执行计划缓存导致的误判 - 如果必须用窗口函数,且只关心总数,至少确保表上有主键或聚簇索引,减少排序成本
真正容易被忽略的点:ORDER BY 不仅影响“算什么”,更决定“按什么顺序算”——而顺序一旦不唯一,结果就不可重现,尤其在线上重跑、分页、导出等场景下,这种不确定性会直接变成数据事故。

















