unbounded preceding锚定分区排序后首行,非全表第一行;必须配合partition by和order by生效,否则退化为单行窗口或报错;rows between unbounded preceding and current row是唯一可靠的滚动汇总写法。

UNBOUNDED PRECEDING 不是“从表头开始”,而是分区逻辑起点的锚点
它不指向整张表第一行,只锚定 PARTITION BY 后每个分区内、按 ORDER BY 排序后的首行。漏写 PARTITION BY 或 ORDER BY,UNBOUNDED PRECEDING 就失去意义——此时数据库可能退化为单行窗口,甚至报错。
常见静默失败现象:查询无报错,但 SUM() OVER (... UNBOUNDED PRECEDING ...) 每行结果都等于当前行值,像没累加。大概率是以下之一:
- 完全没写
ORDER BY:SQL 标准规定,无ORDER BY时默认帧为ROWS BETWEEN CURRENT ROW AND CURRENT ROW,UNBOUNDED PRECEDING被忽略 - 写了
ORDER BY但字段有大量重复值(如status枚举),又用了默认RANGE帧,导致多行被捆进同一窗口,行为不可控 - 误写成
0 PRECEDING:PostgreSQL 报frame starting offset must not be negative,SQL Server 直接拒绝解析;这不是笔误,是语义错误——0 PRECEDING不合法,也不等价于UNBOUNDED PRECEDING
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 是滚动汇总唯一可靠写法
想做累计和、累计计数、移动平均起点,必须显式写出完整帧子句。省略 ROWS 或依赖默认行为,跨库风险极高:
- MySQL 8.0+ 默认是
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,相同排序值全被合并计算 - SQL Server 默认也是
RANGE,而 PostgreSQL 和 BigQuery 在无显式声明时可能降级为RANGE或报错 -
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW才真正实现“逐行可复现”的物理行偏移累加
示例(安全写法):
SUM(amount) OVER ( PARTITION BY user_id ORDER BY order_time, order_id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
注意二级排序 order_id:避免 order_time 重复时结果非确定。
UNBOUNDED PRECEDING 触发全分区缓存,性能代价必须前置评估
它不是“慢一点”,而是让优化器放弃流式处理,强制缓存整个分区数据再逐行输出。实际执行中常表现为:
-
WindowAgg节点的Actual Rows达到输入行数 × 分区数(比如 10 万行 × 1 万用户 = 10 亿行扫描) - PostgreSQL 出现
Sort Method: external merge,work_mem不足时写 GB 级临时文件 - SQL Server
tempdb日志暴涨,伴随sort warning事件 - MySQL 8.0.17 前退化为 O(n²) 关联模拟,索引也难救
关键缓解手段:
- 对
PARTITION BY字段(如user_id)建 B-tree 索引,可能触发 merge-join 路径 - 若
ORDER BY是表达式(如date_trunc('month', created_at)),必须建函数索引:CREATE INDEX idx_orders_month ON orders (date_trunc('month', created_at)) - 绝对避免在无索引的
ORDER BY字段上用UNBOUNDED PRECEDING——此时排序 + 累加双重开销,最坏接近 O(n²)
RANGE vs ROWS 下 UNBOUNDED PRECEDING 行为差异极大
二者语义根本不同:ROWS 按物理位置跳指针,RANGE 按排序值动态扩缩窗口。当排序字段基数低时,RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 可能让第一行就拉入上千行,后续每行都重扫。
实测中相同查询,RANGE 比 ROWS 慢 5–10 倍。只有明确需要“同值同处理”(如并列排名、PERCENT_RANK())才用 RANGE;滚动汇总、累计统计一律用 ROWS。
时间序列场景更危险:写 RANGE BETWEEN INTERVAL '7' DAY PRECEDING AND CURRENT ROW,若日期字段有重复(比如一天 5000 笔订单),窗口会失控膨胀。MySQL 不支持日期型 RANGE,PostgreSQL 支持,SQL Server 完全不支持——跨库迁移时默认帧极易出错。
真正难的不是写对语法,而是意识到 UNBOUNDED PRECEDING 的代价藏在执行计划里,且一旦和 RANGE、无索引 ORDER BY、高基数分区组合,性能崩塌是静默发生的。










