ROWS按物理行数切片,RANGE按排序值的逻辑范围切片;ROWS取固定行数,RANGE聚合相同或相近值的所有行,且易因重复值或隐式退化导致结果偏差。

ROWS 和 RANGE 的本质区别在哪
ROWS 按物理行数切片,RANGE 按排序值的逻辑范围切片。比如 ORDER BY score 后,ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING 总是取前后各 1 行;而 RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING 会把所有 score 在当前行 ±1 区间内的行全拉进来——哪怕有 10 行分数相同,就真给你算 10 行。
常见错误现象:RANGE 在没显式指定 ORDER BY 列类型时自动退化为 UNBOUNDED PRECEDING AND CURRENT ROW,且不报错;你查了半天发现窗口结果“少算”,其实是 RANGE 把重复值全吞了又没提示。
使用场景:
-
ROWS更适合滑动平均、最近 N 笔订单统计这类明确要“看几行”的需求 -
RANGE更适合按时间/金额区间聚合,比如“过去 7 天内所有订单”或“价格在当前商品 ±50 元内的竞品”
动态控制窗口边界的三种可行方式
SQL 标准不支持把 ROWS 或 RANGE 的偏移量写成变量或表达式(比如 ROWS BETWEEN ? PRECEDING AND ? FOLLOWING),但有绕过办法:
- 使用
EXECUTE IMMEDIATE(PostgreSQL / Oracle)或PREPARE + EXECUTE(MySQL 8.0+)拼接 SQL 字符串,再执行。注意 SQL 注入风险,必须对数字参数做类型强转或白名单校验 - 在应用层生成不同固定窗口的视图或 CTE,比如预定义
window_3_rows、window_7_days,运行时选一个 - 用
FILTER子句配合大范围窗口 + 条件过滤(PostgreSQL 9.4+),例如先开ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING,再用AVG(val) FILTER (WHERE abs(diff_day)
性能影响:动态拼接 SQL 会破坏查询计划缓存;FILTER 方式看似灵活,但实际扫描行数远超必要,小数据集没问题,千万级表慎用。
ORDER BY 必须存在,且不能含不确定值
ROWS 和 RANGE 都强制要求 ORDER BY 子句。但很多人忽略一点:如果 ORDER BY 列含 NULL、或用了 RANDOM()、NOW() 这类非确定性函数,窗口行为会不可预测。
常见错误现象:
-
ORDER BY created_at DESC NULLS LAST没写NULLS LAST,导致NULL被排到最前,CURRENT ROW实际位置漂移 -
ORDER BY id, RANDOM()导致每次执行窗口边界跳变,同一行在不同执行中可能被计入或排除
正确做法:
- 对含
NULL的列显式声明NULLS FIRST或NULLS LAST - 避免在窗口
ORDER BY中使用任何非确定性函数 - 如果业务上确实需要打散顺序,先用子查询加
ROW_NUMBER()固定序号,再在外层按该序号开窗
MySQL 8.0 的 RANGE 不支持 VALUE 偏移
MySQL 8.0 支持 RANGE,但只接受 UNBOUNDED PRECEDING、CURRENT ROW、UNBOUNDED FOLLOWING 三种写法,不支持 RANGE BETWEEN INTERVAL 7 DAY PRECEDING AND CURRENT ROW 这种标准语法。
常见错误现象:直接照搬 PostgreSQL 写法,报错 ERROR 1064 (42000): You have an error in your SQL syntax,但错误信息里根本没提 RANGE 不支持值偏移。
替代方案:
- 改用
ROWS+ 时间戳转为序号(比如用DATEDIFF算天数差,再ROW_NUMBER() OVER (ORDER BY date)) - 用自连接或 LATERAL JOIN 模拟范围,但性能差,只适用于小表
- 升级到 MySQL 8.0.29+ 并确认是否启用了
range_optimization(仍不完全等价于标准 RANGE)
复杂点在于:不同数据库对 RANGE 的实现粒度差异很大,PostgreSQL 支持 VALUE 偏移,SQL Server 完全不支持 RANGE,Oracle 支持但要求 ORDER BY 列必须是数值或日期——别指望一套 SQL 到哪都跑得通。










