mysql 8.0中range按值范围分组(同值行合并处理),rows按物理行序定位(值相同也独立计算);默认使用rows,显式声明可避免语义错误与性能陷阱。

OVER子句里 RANGE 和 ROWS 的区别到底在哪
MySQL 8.0 默认用 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,不是 RANGE。很多人写 OVER (ORDER BY ts) 后发现累计求和结果不对,就是误以为它按值分组累加(RANGE 行为),实际是按行位置累加(ROWS 行为)。
关键差异:
-
RANGE:把ORDER BY列值相同的行视为“同一组”,窗口边界按值判断(比如所有ts = '2023-01-01'的行一起进/出窗口) -
ROWS:严格按物理排序后的行号定位,值相同也各自独立,CURRENT ROW就是当前这行,不拉其他人 - MySQL 8.0 不支持
RANGE配合非数字/非日期类型的ORDER BY列(会报错ER_NOT_SUPPORTED_YET)
什么时候必须显式写 ROWS 或 RANGE
只要窗口计算逻辑依赖“值相等是否合并处理”,就必须显式声明。比如做移动平均、累计计数、或想排除重复时间戳导致的跳跃——不写就容易跑偏。
常见场景与写法:
- 按时间戳累计销售额(同秒内多笔订单要合并)→ 用
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW - 取最近 3 条记录的平均值(不管时间是否重复)→ 必须写
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW - 只对当前值及之前所有同值行求和 →
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW(且ORDER BY列需支持范围比较,如INT、DATETIME) - 省略
ROWS/RANGE时,MySQL 永远按ROWS解析,哪怕你心里想的是值语义
ORDER BY 缺失或 NULL 值引发的隐性错误
OVER() 里没写 ORDER BY,多数聚合类窗口函数(如 SUM()、AVG())会把整个分区当一个窗口,但行为不可控;更危险的是,如果 ORDER BY 列含 NULL,MySQL 8.0 默认把 NULL 排在最前面(NULLS FIRST),而 RANGE 窗口遇到 NULL 直接报错 ER_INVALID_WINDOW_RANGE_BOUND。
稳妥做法:
- 所有带
RANGE的OVER子句,先用WHERE col IS NOT NULL过滤,或用COALESCE(col, '1970-01-01')填充 - 显式声明排序方向:
ORDER BY amount DESC NULLS LAST(MySQL 8.0.22+ 支持NULLS FIRST/LAST) - 避免在
RANGE中使用字符串类型排序列——即使能执行,语义也极易误解(按字典序范围?谁定义“前一个字符串”?)
性能陷阱:RANGE 在大数据量下可能比 ROWS 慢一个数量级
MySQL 对 ROWS 窗口可用游标逐行推进,复杂度 O(n);但 RANGE 需对每个当前行做范围查找(类似多次二分搜索),尤其当 ORDER BY 列高基数或存在大量重复值时,I/O 和 CPU 开销陡增。
实测提示:
- 千万级表上,
RANGE累计求和比等效ROWS慢 5–8 倍,且无法用索引优化(窗口函数本身不走索引) - 如果业务允许近似结果,用
ROWS BETWEEN 99 PRECEDING AND CURRENT ROW模拟“最近 100 条”比用RANGE INTERVAL 1 DAY PRECEDING稳定得多 -
RANGE无法和PARTITION BY外的索引协同,别指望加个联合索引就能加速它
真正要用 RANGE,得确认数据分布足够稀疏、重复值极少,且业务语义强依赖“值区间”而非“行位置”。否则,多数时候是自找麻烦。











