显式指定 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 比默认帧快,因其触发增量计算、复用中间状态;而默认 RANGE 帧易引发全分区扫描、重复值膨胀窗口、额外排序及平方级代价。

ROWS BETWEEN UNBOUNDED PRECEDING 为什么比默认帧快?
因为显式写 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 能触发数据库的增量计算机制,而漏写帧子句时,数据库会按规则补默认值——有 ORDER BY 就补 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,没 ORDER BY 就补 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING。这两个默认帧都容易导致全分区扫描。
常见错误现象:EXPLAIN (ANALYZE, BUFFERS) 显示 Sort Method: external merge 或 temp written=xxx MB;PostgreSQL 中 actual rows 高达输入行数 × 窗口函数个数(比如 1000 万 × 2)。
- 默认
RANGE帧在排序列有重复值时(如多个订单同属order_date = '2024-01-01'),会把所有同值行一次性拉进窗口,实际窗口远超预期 -
UNBOUNDED FOLLOWING强制每行都重算整个分区聚合,物理执行代价呈平方级增长 - 显式
ROWS帧让优化器能跳指针、复用前一行中间状态(sum()加当前值,max()比较当前值与上一行结果)
ROWS 和 RANGE 在执行层面到底差在哪?
ROWS 按物理行号定位,数据库只需维护一个游标偏移量;RANGE 按排序列的值做范围查找,每行都要重新比对值边界,还常触发额外排序。
尤其当排序字段基数低(如状态码、枚举值)或存在大量重复时,RANGE 帧的实际窗口可能膨胀数倍,I/O 和 CPU 开销陡增。
- 时间序列场景下,
RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW看似合理,但若sale_date有重复,当天所有行全被包入窗口,无法退回到“最近7行”语义 - 想算“前2行+当前行”的移动平均,必须用
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW;写成RANGE不仅慢,还可能因重复值包含意外行 - MySQL 8.0 对
RANGE帧支持有限(不认INTERVAL),PostgreSQL 和 Snowflake 支持,SQL Server 则不支持日期型RANGE
为什么 ORDER BY 缺失或带 NULL 就会崩?
ROWS BETWEEN 必须依赖确定性排序,否则“前一行”“后一行”无定义。不写 ORDER BY,MySQL 8.0+ 直接报错 Window 'w' lacks an ORDER BY clause,PostgreSQL 报 Window definition requires an ORDER BY clause。
即使写了 ORDER BY,若列含大量 NULL,不同引擎处理方式不一致:NULLS FIRST 和 NULLS LAST 混用会导致 CURRENT ROW 位置漂移,累计值错位。
-
ORDER BY字段最好有索引(如CREATE INDEX ON events (event_time, id)),避免隐式Sort节点 - 原始数据若存在相同时间戳,建议追加唯一键:
ORDER BY event_time, event_id - 别用表达式排序(如
ORDER BY RANDOM()或ORDER BY UPPER(name)),会阻止索引使用且破坏确定性
哪些帧写法是性能雷区?
只要帧定义里出现 UNBOUNDED FOLLOWING,基本就是性能雷区。包括 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING 和 RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING —— 它们语义上要求“对每一行都重算整个分区聚合”,引擎无法增量,只能走全量物化路径。
另一个隐蔽陷阱是:只写 UNBOUNDED PRECEDING 却没配合适当的结束边界,比如 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 是安全的,但若 ORDER BY 列无索引或重复率极高,PostgreSQL 仍可能插入 Stable Sort 节点,MySQL 8.0.17 前会回退到 O(n²) 模拟逻辑。
- 报错
frame start cannot be greater than frame end通常是因为写成5 PRECEDING AND 1 PRECEDING这类方向/数值矛盾的帧 - 想取“最近3行”,应写
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW,不是3 PRECEDING AND CURRENT ROW(那是4行) - 滚动统计中首尾行自动截断是正常行为;若需强制固定行数(不足则返回
NULL),得配合COUNT(*) OVER (...)+CASE WHEN
EXPLAIN 的 Buffers 和 temp written 里藏不住。

















