ROWS按物理行位置切片,RANGE按ORDER BY列值大小切片;前者逐行计算,后者将重复值归为一组,LAG/LEAD仅支持ROWS,MySQL中RANGE不支持时间偏移,隐式默认为RANGE易致结果偏差与性能下降。

ROWS是行号切片,RANGE是值域切片
根本不是同一种“范围”:ROWS数的是物理行位置(第1行、第2行……),RANGE比的是ORDER BY列的值大小(比如所有date = '2026-05-01'的行算作一组)。当排序字段有重复值(如多笔订单同秒下单、日志按天聚合),RANGE会把它们全打包进同一个窗口帧,而ROWS仍一行一行往下数。
常见错误现象:
-
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无法定义“上一行”
MySQL里RANGE对DATE类型直接报错
MySQL 8.0+不支持RANGE BETWEEN INTERVAL '7 days' PRECEDING这种写法,尤其对DATE类型会抛出ERROR 3589 (HY000): Window frame 'RANGE' with offset must be of type numeric。它要求偏移量必须是数值型,不能是时间间隔。
绕过方法:
- 把日期转成整数:
TO_DAYS(event_time)或UNIX_TIMESTAMP(event_time) - 改用
ROWS+ 预先生成连续日期表补缺(适合“过去7天”类需求) - 确认业务是否真需要值域逻辑——很多场景其实用
ROWS更稳
默认没写就是RANGE,但没人告诉你
几乎所有主流数据库(PostgreSQL/MySQL 8.0+/SQL Server)在你只写SUM(x) OVER (ORDER BY ts)时,自动补上RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。你以为只是简单排序累加,实际跑的是值域逻辑。
这导致两个隐形问题:
- 结果在重复值多时完全偏离预期(比如节假日累计销售额突增)
- 性能可能断崖下跌:
RANGE对每行都要重新扫描满足值条件的边界,最坏O(n²);ROWS是游标偏移,O(1) - 执行计划里未必明显提示,得看
EXPLAIN ANALYZE中WindowAgg节点是否带Range字样
什么时候非用RANGE不可
不是性能优先就选ROWS,有些语义天然依赖值域:
- “过去7天所有订单金额总和” → 必须用
RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW。用ROWS BETWEEN 7 PRECEDING会变成“最近7笔订单”,跟时间无关 - “价格±50元内的竞品均价” →
RANGE BETWEEN 50 PRECEDING AND 50 FOLLOWING,这里50是数值差,不是行数 - “所有
score ≥ 当前用户 score的人数” →RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING,靠值比较而非位置
强行用ROWS替代,结果在数据分布不均时必然出错——高峰期多拉、低谷期漏算,且难以复现。真正容易被忽略的,是ORDER BY字段含NULL或非确定性函数时,两者都会失效,但数据库不报错,只默默返回不可靠结果。

















