ROWS按行号计数,RANGE按排序值分组;ROWS逐行处理,RANGE将同值行归为同一逻辑点;RANGE适用于值域范围计算(如7天内、价格±50),ROWS适用于位置偏移(如前5行);MySQL对RANGE日期支持有限,需转数值处理。

ROWS按行号计数,RANGE按排序值分组
根本区别不在语法,而在计算逻辑:ROWS只看“这是第几行”,RANGE只看“这个ORDER BY值等于多少”。比如ORDER BY date后有三行都是'2026-05-01',ROWS会把它们当作三行独立处理;RANGE则视作一个逻辑点,所有同值行自动打包进同一窗口帧。
常见错误现象:
-
SUM(sales) OVER (ORDER BY date RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)在某天有50笔订单时,当天所有sales一次性累加,然后跳到下一天;换成ROWS则是逐笔加总,中间不跳 -
LAG(amount, 1) OVER (ORDER BY price RANGE)直接报错或返回NULL——LAG/LEAD强制要求ROWS,RANGE无法定义“上一行” - 没写显式帧时,数据库默认补
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,你以为是逐行累加,实际是按日期值跳变
MySQL 8.0对RANGE + 日期的支持很有限
别照搬PostgreSQL写法。MySQL 8.0+不支持RANGE BETWEEN INTERVAL '7' DAY PRECEDING AND CURRENT ROW这类语句,会报错:ERROR 3589 (HY000): Window frame 'RANGE' with offset must be of type numeric。
实操建议:
- 若需日期范围窗口,先用
TO_DAYS(order_date)转为整数列,再ORDER BY days_col RANGE BETWEEN 6 PRECEDING AND CURRENT ROW - 避免在
ORDER BY中用DATETIME或DATE直连RANGE,除非你确认字段已转为数值型 - 测试时用
SELECT COUNT(*) GROUP BY order_date检查重复程度——重复越多,RANGE越容易“吞行”
什么时候必须用RANGE,不能硬换ROWS
不是所有场景都能靠改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元内的竞品”,依赖值域而非位置 - 按等级分档统计:
COUNT(*) OVER (ORDER BY score_level RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING),要包含所有同等级及更高等级的行
强行用ROWS BETWEEN 200 PRECEDING模拟7天,高峰期单日200单就漏数据,低峰期又拉入无关行。
性能差异远比结果差异更隐蔽
RANGE慢不是偶然,是设计使然。它对每一行都要重查满足值条件的边界,最坏情况是O(n²);ROWS靠游标偏移定位,基本是O(1)。
容易被忽略的点:
- 执行计划里看到
WindowAgg节点带Range字样(PostgreSQL)或Sort + Window两阶段(SQL Server),基本就是RANGE在拖慢 - MySQL的
EXPLAIN中出现Using filesort,说明没走索引,RANGE会雪崩 - 复合索引必须严格按
PARTITION BY字段在前、ORDER BY字段紧随其后的顺序,比如(user_id, event_time),反过来无效
真正难的不是写对语法,而是判断业务逻辑到底依赖“位置”还是“值域”——比如“最近5笔订单”是位置问题,用ROWS;“最近5天所有订单”是值域问题,RANGE才自然。而多数人直到上线后发现累计值突增、移动平均压低、LAST_VALUE总返回当前行,才意识到窗口帧早就不对了。

















