range需对每行重复扫描满足值条件的行,最坏o(n²);rows仅行号偏移,稳定o(1)。因range按值域匹配而非定位,重复值越多,每次拉入行数越不可控,性能断崖下跌。

因为 RANGE BETWEEN 每次都要重新扫描满足值条件的行,而 ROWS BETWEEN 只需按行号偏移定位——前者最坏是 O(n²),后者稳定 O(1)。
为什么 RANGE 的执行要重扫多遍?
RANGE 不是“跳到某一行”,而是“找出所有 ORDER BY 值落在指定范围内的行”。数据库对每一行都得做一次值域匹配:比如 SUM(sales) OVER (ORDER BY sale_date RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW),对第 1000 行,它得从头开始查哪些行的 sale_date ≥ 第 1000 行日期减 7 天;第 1001 行又得重查一遍。重复值越多,每次匹配拉进的行数越不可控。
- ROWS 只靠排序后游标 + 行号偏移,一次定位,不重复扫描
- RANGE 在 PostgreSQL 的执行计划里会显示
WindowAgg (Range),SQL Server 显示Sort + Window两阶段,都是性能预警信号 - MySQL 8.0+ 中若没走索引,
EXPLAIN会出现Using filesort,RANGE 窗口直接雪崩
重复值会让 RANGE 性能断崖式下跌
当 ORDER BY 列存在大量重复(如 status、DATE(created_at)),RANGE 会把所有同值行打包成一个逻辑点。比如某天有 500 条日志,RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 对当天任意一行,都会把这 500 行全拉进来参与聚合——而 ROWS 同样窗口只会取前 N 行,完全不受影响。
- 用
SELECT COUNT(*) FROM t GROUP BY order_col先看重复程度,若最大分组 > 100,RANGE 风险极高 - MySQL 对
DATE类型直接写RANGE BETWEEN INTERVAL '7 days' PRECEDING会报错ERROR 3589,必须先转成TO_DAYS(date_col) - PostgreSQL 中 RANGE 窗口在大数据集上常比等效 ROWS 慢 3–5 倍,尤其无索引时
什么情况下你其实不能换 ROWS?
不是所有慢的 RANGE 都能简单替换成 ROWS——语义错了比慢更致命。以下场景 RANGE 不可替代:
-
SUM(revenue) OVER (ORDER BY event_time RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW):业务要的是“过去 7 天内所有订单”,不是“最近 7 行订单” -
AVG(price) OVER (ORDER BY price RANGE BETWEEN 50 PRECEDING AND 50 FOLLOWING):价格 ±50 元竞品均价,和行数无关 - 按分类等级(A/B/C)分组累计:
COUNT(*) OVER (ORDER BY score_level RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING),RANGE 才能把所有 A 级及更高档位合并计算
真正卡住人的,从来不是语法怎么写,而是想清楚:你的业务指标依赖的是“位置”(第几笔、前几行)还是“值域”(某时间区间、某数值范围)——选错维度,优化再猛也白搭。










