rows按物理行数切片,range按排序字段值范围切片;rows取当前行及前2行共3行,range取当前行时间前2天内所有行,重复值导致range吞并多行而rows严格按序,故业务语义不同。

直接说结论:ROWS 按物理行数切片,RANGE 按排序字段的值范围切片;用错一个,结果可能完全偏离业务本意,且不报错。
为什么 ROWS 和 RANGE 返回结果不同?
根本原因在于“窗口边界怎么算”:
-
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW:不管时间戳或数值是否重复,只取当前行和它前面紧挨着的 2 行(共 3 行) -
RANGE BETWEEN INTERVAL '2 days' PRECEDING AND CURRENT ROW:把所有ORDER BY字段(比如event_time)落在“当前行时间减去 2 天”到“当前行时间”之间的所有行都拉进来——哪怕中间缺了 1 天、某天有 50 笔交易,它也全包
当 event_time 有重复(如毫秒级日志聚合到秒级、或多个订单同秒下单),RANGE 会一次性吞掉所有同值行,而 ROWS 仍严格按行号走。这就是累计求和在节假日突然跳升、移动平均在爆发日异常放大等现象的根源。
什么时候必须用 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 替代,结果在数据分布不均时必然出错——高峰期多拉、低谷期漏算,且难以复现。
为什么加了 ROWS 后查询快了很多?
因为执行机制完全不同:
-
ROWS:排序后靠游标偏移定位,O(1) 随机访问,基本不额外扫描 -
RANGE:对每一行,都要在已排序序列里重新线性查找满足值条件的边界,最坏 O(n²),尤其当ORDER BY字段大量重复(如DATE(created_at))时,性能断崖下跌 - 多数数据库(PostgreSQL/MySQL 8.0+/SQL Server)默认隐式补
RANGE,你写SUM(x) OVER (ORDER BY ts)实际跑的是RANGE逻辑,但执行计划里未必明显提示
上线前务必用真实数据量跑 EXPLAIN ANALYZE,对比加 ROWS 前后的 Actual Total Time——差 3–10 倍很常见。索引能缓解,但不能消除本质差异。
容易被忽略的关键限制
这些坑不会报语法错误,但一跑就崩或结果错得离谱:
-
RANGE要求ORDER BY字段必须可比较且非字符串(MySQL/PostgreSQL 对VARCHAR不支持RANGE边界计算) - 没写
ORDER BY,RANGE直接报错(ERROR: RANGE is only supported with ORDER BY),而ROWS可以无序运行(但结果不可控) -
ORDER BY字段含NULL或非确定性函数(如NOW()、RAND()),两者都可能返回非预期结果 - 用
RANGE前,先SELECT COUNT(*) FROM t GROUP BY order_col ORDER BY COUNT(*) DESC LIMIT 5看看重复程度——如果 top 5 分组占了 60% 行数,RANGE几乎必然慢且难调
真正卡住人的从来不是语法,而是想清楚:你的业务问题,到底是在问“第几笔”还是“哪段时间/哪个区间”。答错这个问题,后面全白搭。










