rows between unbounded preceding and current row 快,因其支持增量计算,可复用前一行中间状态(如cumsum累加、max比较),避免全分区重扫;而含unbounded following的帧强制全量物化,性能呈平方级下降。

因为ROWS BETWEEN UNBOUNDED PRECEDING(配合CURRENT ROW或FOLLOWING)能触发增量计算,而UNBOUNDED FOLLOWING强制全分区重扫,物理执行代价呈平方级增长。
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 为什么快
这个帧定义允许数据库复用前一行的中间状态:sum() 可以在上一行 cumsum 基础上加当前值,max() 只需比较上一行 max 和当前值。PostgreSQL 的 windowagg 节点会启用 incremental 策略,避免重复扫描。
实操建议:
- 必须带
ORDER BY,且排序列最好有索引(如CREATE INDEX ON sales (order_date, id)) - 避免在
ORDER BY中混用NULLS FIRST和NULLS LAST,否则 CURRENT ROW 位置可能漂移 - 在 MySQL 8.0.17+、PostgreSQL 12+、SQL Server 2016+ 上该写法均被优化,但 Hive/Spark SQL 需确认版本是否启用
unbounded_preceding_optimization规则
UNBOUNDED FOLLOWING 为何是性能雷区
当帧包含 UNBOUNDED FOLLOWING(如 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING),语义上要求“对每一行都重算整个分区聚合”,引擎无法增量,只能走 sorted 模式 + 全量 materialize。
常见错误现象:
- PostgreSQL 中
actual rows显示为输入行数 × 窗口函数个数(如 2400万 × 3) - 执行计划出现
Materialize+Sort (stable)节点,Buffers: shared read=xxx, temp written=yyy - Linux
perf top显示memcpy和qsort_r占 CPU 60% 以上
ROWS vs RANGE 在 UNBOUNDED 场景下的性能分水岭
ROWS 按物理行号切片,边界确定、内存访问局部性好;RANGE 需对每行做值范围扫描(比如 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 要反复查找所有 ≤ 当前行 order_date 的记录),尤其在 order_date 重复率高时,I/O 和 CPU 开销陡增。
实操建议:
- 累计求和、排名、滚动最大值等场景,无条件选
ROWS - 只有真正需要“值区间语义”(如“过去7天销售额”)才用
RANGE BETWEEN INTERVAL '7' DAY PRECEDING AND CURRENT ROW - 测试时用
EXPLAIN (ANALYZE, BUFFERS)对比两者的temp written和Execution Time
容易被忽略的隐式陷阱
即使只写 UNBOUNDED PRECEDING,若 ORDER BY 列无索引或存在大量重复值,仍可能退化:PostgreSQL 会在重复值块内强制稳定排序,导致额外 Sort 节点;MySQL 8.0.17 前对重复 ORDER BY 值会回退到 O(n²) 模拟逻辑。
关键检查点:
- 执行
SELECT count(*), count(DISTINCT order_date) FROM sales,若两者接近,ROWS安全;若重复率 > 15%,考虑加唯一辅助排序键(如ORDER BY order_date, id) - 避免在窗口函数中嵌套子查询或标量函数(如
ORDER BY DATE(created_at)),这会让优化器放弃帧优化 - 分区过大时(>500万行),宁可拆成多个小窗口
PARTITION BY date_trunc('month', order_date),也不硬扛UNBOUNDED FOLLOWING











