ROWS按物理行号切片,RANGE按ORDER BY值范围切片;ROWS适合“最近N笔订单”等位置语义场景,RANGE适合“过去N天销售额”等值域语义场景,隐式默认RANGE易致性能与结果偏差。

ROWS 和 RANGE 看似只差一个字,但实际行为完全不同:ROWS 按物理行号切片,RANGE 按 ORDER BY 列的值范围切片;用错一个,结果可能完全偏离业务本意,且不报错。
ROWS BETWEEN 是“数行”,不是“看时间”
它不管排序字段的值是否重复、跨度多大,只严格按排序后的位置取固定行数:
-
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW总是取当前行 + 前面紧挨着的 2 行(共 3 行),哪怕这 3 行的时间相隔 30 天 - 适合“最近 5 笔订单”“前 3 名用户”这类明确要“看几行”的场景
- 对
LAG/LEAD必须用ROWS,RANGE会报错或返回NULL - 性能稳定:排序后靠游标偏移定位,O(1) 随机访问,有索引时基本不额外扫描
RANGE BETWEEN 是“比值”,不是“数位置”
它把所有 ORDER BY 值落在指定区间内的行全拉进来,不管有多少行、是否重复:
-
RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW会包含所有event_time在 [当前行时间 − 7 天, 当前行时间] 内的记录——哪怕某天有 200 笔订单,全算进当前窗口 - 适合“过去 7 天销售额”“价格 ±50 元内竞品均价”这类依赖值域语义的场景
- 当
event_time只精确到秒,而某秒插入 100 条日志,RANGE BETWEEN INTERVAL '1 second' PRECEDING AND CURRENT ROW会一次性吞掉这 100 行;ROWS同样写法只取 2 行 - 性能风险高:每行都要重新查找满足值条件的边界,最坏 O(n²),尤其在
ORDER BY字段大量重复(如DATE(created_at))时
数据库默认补 RANGE,你根本没意识到自己在用它
几乎所有主流数据库(PostgreSQL / MySQL 8.0+ / SQL Server)在你只写 OVER (ORDER BY ts) 时,都会隐式补上 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:
- 你以为只是简单排序累加,实际跑的是值域逻辑
- 执行计划里未必明显提示,得看
EXPLAIN ANALYZE中WindowAgg节点是否带Range字样 - MySQL 8.0+ 不支持
RANGE BETWEEN 7 PRECEDING AND CURRENT ROW这种写法(只认UNBOUNDED或CURRENT ROW),强行写会直接语法报错 - 上线前务必用真实数据量跑
EXPLAIN ANALYZE,对比加ROWS前后的Actual Total Time——差 3–10 倍很常见
ORDER BY 字段类型和 NULL 处理直接影响 RANGE 是否可用
RANGE 对 ORDER BY 字段有硬性要求,不是所有字段都能用:
- 必须是可比较的数值或日期类型(
INT、TIMESTAMP、DATE),字符串(VARCHAR)或布尔型在多数引擎中不支持RANGE边界计算 - 如果
ORDER BY created_at含NULL,又没写NULLS LAST,NULL会被排到最前,导致CURRENT ROW实际位置漂移,RANGE结果错乱 - 绝对不能在
ORDER BY里用NOW()、RANDOM()这类非确定性函数,否则每次执行窗口边界都跳变 - 如果业务上真需要打散顺序,先用子查询加
ROW_NUMBER()固定序号,再在外层按该序号开窗
真正难的不是语法,而是判断业务逻辑到底依赖“位置”还是“值域”——比如“过去 N 笔订单”是位置问题,必须用 ROWS;“过去 N 天内所有订单”是值域问题,RANGE 才安全。重复值、隐式退化、性能断崖,这些坑都不会报错,但一跑就崩或结果错得离谱。

















