ROWS按物理行号切片,严格取指定位置的行;RANGE按排序字段值域切片,将相同值的行视为一点并扩展数值区间。二者逻辑本质不同,误用会导致结果错误且不报错,性能差异显著。

ROWS 和 RANGE 看似只是窗口函数里换一个词,但实际是两种完全不同的计算逻辑:ROWS 数行号,RANGE 比数值;用错一个,结果可能全错,且不报错。
ROWS 是物理行数切片,严格按顺序取“第几行”
ROWS 完全无视排序字段的值,只认排序后的位置编号。比如 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW 就是硬取当前行和它前面紧挨着的 2 行(共 3 行),不管这三行的 order_time 是不是同一天、price 是不是一样。
- 适合“最近 5 笔订单”“前后 3 条日志”这类明确要控制行数的场景
- 重复值不影响行为:哪怕
status = 'done'连续出现 100 次,ROWS 仍一行一行往下数 - 性能稳定:排序后靠游标偏移定位,基本是 O(1) 操作,大数据量下也扛得住
- 所有主流数据库(MySQL 8.0+/PostgreSQL/SQL Server)都完整支持
RANGE 是值域范围切片,按排序字段的值打包分组
RANGE 把所有 ORDER BY 字段值相同的行视为一个逻辑点,然后以当前行的值为中心,向左右扩展指定的值区间。比如 RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW 会拉入所有 event_time 落在“当前行时间减 7 天”到“当前行时间”之间的行——哪怕中间缺了 2 天,或某天有 300 条记录,它全算。
- 适合“过去 7 天销售额”“价格 ±50 元内的竞品均价”这类依赖业务值域的场景
- 重复值会“吞行”:若
created_date只精确到天,某天有 200 条记录,RANGE 会让这 200 行共享同一个累计结果 - 性能风险高:每行都要重新扫描满足值条件的边界,最坏 O(n²),尤其当
ORDER BY字段大量重复时 - MySQL 8.0+ 对
RANGE支持有限:不支持7 PRECEDING这种数字偏移,只认UNBOUNDED或CURRENT ROW;字符串、布尔型字段直接不支持
不写 ROWS 或 RANGE 时,数据库默认补 RANGE
这是最容易踩的坑:你写 SUM(sales) OVER (ORDER BY event_time),没显式声明帧类型,PostgreSQL/MySQL 8.0+/SQL Server 都会悄悄补上 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。你以为只是简单排序累加,实际跑的是值域逻辑。
- 结果偏差:节假日单日订单暴增,RANGE 累计值会突然跳升;ROWS 则逐笔递增
- 性能断崖:EXPLAIN ANALYZE 中看
WindowAgg节点是否带Range字样;真实数据量下,RANGE 可能比等效 ROWS 慢 3–10 倍 - LAG/LEAD 必须用 ROWS:
LAG(amount, 1) OVER (ORDER BY price RANGE)在多数引擎中直接报错或返回 NULL - 上线前必须验证:用
SELECT COUNT(*) GROUP BY order_col查重复程度;再用真实数据跑EXPLAIN ANALYZE对比
真正难的不是语法,而是想清楚业务到底要“按位置算”还是“按值域算”——前者选 ROWS,后者才用 RANGE;混淆这两者,问题往往藏在数据分布变化之后才暴露。

















