ROWS比RANGE快,因ROWS靠行号偏移实现O(1)随机访问,RANGE需对每行重扫值范围、最坏O(n²),尤其ORDER BY列重复值多或无索引时性能断崖下跌。

ROWS比RANGE快,根本不是“优化得好”,而是底层定位方式完全不同:ROWS靠行号偏移,O(1)随机访问;RANGE得对每一行重新扫描值范围,最坏O(n²)。
ROWS是游标跳转,RANGE是逐行重扫
数据库执行SUM(x) OVER (ORDER BY ts ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)时,排序完成后只需用当前游标位置减2、再读3行——本质是数组下标运算,不依赖值内容。
而SUM(x) OVER (ORDER BY ts RANGE BETWEEN INTERVAL '2 days' PRECEDING AND CURRENT ROW)必须对每一行都重新向左线性扫描:从当前行开始,挨个比对ts是否 ≥ current_ts - INTERVAL '2 days',直到越界。重复值越多(比如DATE(created_at)),每轮扫描越长。
- 重复值多 → 同值行被批量纳入窗口,但RANGE内部仍需逐行判断边界 → 扫描量不减反增
- ORDER BY列无索引 → 每次扫描都走全表或文件排序,
Using filesort在EXPLAIN里高频出现 - MySQL 8.0+对
DATE类型不支持RANGE BETWEEN INTERVAL,直接报ERROR 3589,必须先转成TO_DAYS(date_col)
默认隐式补RANGE,你根本不知道自己在慢
几乎所有数据库(PostgreSQL/MySQL 8.0+/SQL Server)在你只写OVER (ORDER BY ts)时,自动补上RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。语法合法,结果可能错,性能断崖下跌,还无任何警告。
典型现象:SUM(revenue) OVER (ORDER BY DATE(event_time))本意是“逐日累加”,实际却是“每组同日期行共享一个累计值”,且执行计划里WindowAgg节点带Range字样(PostgreSQL)或出现Sort + Window双阶段(SQL Server)。
- 上线前务必用真实数据量跑
EXPLAIN ANALYZE,对比Actual Total Time—— RANGE常比等效ROWS慢3–10倍 - 别信“加了索引就没事”:复合索引必须严格是
(PARTITION BY字段, ORDER BY字段)顺序,反过来无效 -
LAG()/LEAD()这类函数强制要求ROWS,RANGE下直接报错或返回NULL
什么场景非用RANGE不可?硬换ROWS会出错
不是所有慢都能靠改ROWS解决。以下语义天然绑定值域,换ROWS等于改需求:
-
SUM(revenue) OVER (ORDER BY event_time RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW)→ “过去7天内所有订单”,高峰期单日200单,ROWS设6 PRECEDING会漏掉193单 -
AVG(price) OVER (ORDER BY price RANGE BETWEEN 50 PRECEDING AND 50 FOLLOWING)→ “价格±50元内竞品均价”,50是数值差,不是行数 -
COUNT(*) OVER (ORDER BY score_level RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)→ “所有≥当前等级的总人数”,等级是分档逻辑,不是排名位置
真正容易被忽略的点:ORDER BY列含NULL或非确定性函数(如NOW()、RAND()),会让ROWS和RANGE都失效——窗口行为不可控,但SQL仍能执行成功。










