rows按物理行号切片,range按order by列值分组;rows逐行计数,range将重复值归为一组,lag/lead仅支持rows,数据库默认补range易致结果偏差与性能下降。

ROWS按行号切片,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无法定义“上一行”
ORDER BY字段重复时,RANGE窗口会“吞行”,ROWS不会
假设event_time字段只精确到秒,某秒内插入100条日志。用RANGE BETWEEN INTERVAL '1 second' PRECEDING AND CURRENT ROW,这100条全被拉进当前窗口;而ROWS BETWEEN 1 PRECEDING AND CURRENT ROW只取前1行+当前行,共2行。
业务影响很直接:
- 移动平均:用
RANGE算“过去1小时均价”,高峰期会突然拉入几百条记录,均值被压低;用ROWS则稳定取最近N条,但可能跨时间不均匀 - 分段计费:用
ROWS对duration排序后累计,相同通话时长的用户可能被错分到不同档位;RANGE才能保证“≤当前时长”的所有记录都被纳入 -
FIRST_VALUE(price) OVER (ORDER BY tier_start RANGE ...)能准确定位价格档位;换成ROWS可能取到上一档的price
数据库默认补RANGE,不写显式帧定义=悄悄踩坑
几乎所有主流数据库(PostgreSQL/MySQL 8.0+/SQL Server)在你只写OVER (ORDER BY ts)时,自动补上RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。你以为只是简单排序累加,实际跑的是值域逻辑。
这意味着:
- 没报错,但结果在重复值多时完全偏离预期(比如节假日累计销售额突增)
- 性能可能断崖下跌:RANGE对每行都要重新扫描满足值条件的边界,最坏
O(n²);ROWS是游标偏移,O(1) - 执行计划里未必明显提示,得看
EXPLAIN ANALYZE中WindowAgg节点是否带Range字样 - MySQL 8.0+还不支持
RANGE BETWEEN 7 PRECEDING AND CURRENT ROW这种写法,只认UNBOUNDED或CURRENT ROW,强行写会语法报错
什么时候必须用RANGE?什么时候死守ROWS?
别凭直觉选,看语义是否绑定“值区间”:
- 必须用
RANGE:SUM(revenue) OVER (ORDER BY event_time RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW)(过去7天总营收)、AVG(price) OVER (ORDER BY price RANGE BETWEEN 50 PRECEDING AND 50 FOLLOWING)(竞品价格±50元均价) - 必须用
ROWS:AVG(x) OVER (ORDER BY dt ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)(严格最近7条记录均值)、NTILE(10) OVER (ORDER BY score ROWS UNBOUNDED PRECEDING)(按行序分十分位,避免同分挤占档位) - 两者都行但结果不同:累计求和。如果业务要“按时间顺序逐笔累加”,用
ROWS;如果要“截至当前时间点的所有数据累加”,用RANGE
真正容易被忽略的点是:ORDER BY字段含NULL、或用了非确定性函数(如NOW()),会让RANGE和ROWS都失效——窗口无法稳定排序,结果不可复现。











