默认帧常导致全分区扫描:有order by时默认range between unbounded preceding and current row,无时默认rows between unbounded preceding and unbounded following,二者均易引发i/o与内存开销线性增长。

默认帧行为常导致全分区扫描
没写 FRAME 子句时,数据库按规则补默认值:有 ORDER BY 时默认是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,无 ORDER BY 时默认是 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING。这两种默认都可能让窗口覆盖整个分区——哪怕你只想要“最近3行”。结果就是每行都要扫描全部分区数据,I/O 和内存开销随分区大小线性增长。
- 比如计算部门内薪资累计和,若漏写
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,而只依赖默认RANGE帧,遇到相同 salary 值时会把所有同薪员工全拉进来,实际范围远超预期 - 在千万级用户表上按
user_id分区做ROW_NUMBER(),若没显式限制帧且带ORDER BY created_at,默认RANGE帧仍可能触发全分区排序+扫描 - 用
EXPLAIN看执行计划,若WindowAgg节点显示Partitions: 100000+或Sort Method: external merge,基本就是默认帧惹的祸
ROWS 和 RANGE 的性能差异巨大
ROWS 按物理行偏移定位,数据库只需跳指针;RANGE 按 ORDER BY 列的值比较,必须逐行比对、处理重复值,还常触发额外排序。尤其当排序字段基数低(如状态码、枚举值)或存在大量重复时,RANGE 帧的实际窗口可能膨胀数倍。
- 时间序列场景下,
RANGE BETWEEN INTERVAL '7' DAY PRECEDING AND CURRENT ROW看似合理,但若sale_date有重复(同天多笔订单),就会把当天所有行全包进窗口,无法退回到“最近7行”语义 - 想算“前2行+当前行”的移动平均,必须用
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW;写成RANGE不仅慢,还可能因排序键重复而包含意外行 - MySQL 8.0 对
RANGE帧支持有限,PostgreSQL 允许INTERVAL,SQL Server 则不支持日期型RANGE——跨库迁移时默认帧更易出错
显式帧能触发局部计算与索引利用
当你明确写出窄范围帧(如 ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING),优化器更容易推断出只需读取相邻几行,从而避免全分区排序,并可能利用 PARTITION BY + ORDER BY 复合索引的局部扫描能力。
- 索引
(dept_id, hire_date)在SELECT ..., AVG(salary) OVER (PARTITION BY dept_id ORDER BY hire_date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)中可被高效使用;换成默认RANGE帧,优化器可能放弃索引走全表扫描 - 在分布式数据库中,窄帧能让计算尽量落在单个分片内,减少跨节点数据移动;宽帧或默认帧则大概率触发 shuffle
- 物化视图或缓存层对带显式帧的查询更友好——范围固定意味着结果可复用,而默认帧行为随数据分布动态变化,难缓存
UNBOUNDED 不等于“快”,而是“边界不可达”
UNBOUNDED PRECEDING 和 UNBOUNDED FOLLOWING 不是性能开关,它们只是锚定分区边界。真正影响性能的是“从哪到哪”——如果这个“哪”是分区头尾,那还是得扫全分区。
-
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW是累计和刚需,但必须确保分区足够小(比如按天分区),否则照样慢 -
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING在同一ROWS帧里直接报错(ERROR 3578),别硬凑;真要全分区聚合,用SUM() OVER (PARTITION BY x)更直白 - 别迷信
UNBOUNDED——它解决的是逻辑正确性,不是性能问题。性能靠的是控制帧宽度、选对ROWS/RANGE、配好索引
ORDER BY 就以为万事大吉,其实真正卡脖子的,常藏在没写的那一行 ROWS BETWEEN ... 里。











