登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  MySQL

窗口函数 ROWS 与 RANGE怎么配置或排查

来源:17golang原创

时间:2026-09-13 07:38:40 387浏览 收藏

MySQL 窗口函数里,ROWSRANGE 的关键区别不是写法长短,而是“当前行附近”到底按行号计算,还是按 ORDER BY 的值计算。明细逐行累计通常选 ROWS;要把排序值相同的记录视为同一组,或按金额、日期区间取滚动范围,才考虑 RANGE。结果突然多算一笔,优先检查排序列是否有并列值,以及是否依赖了默认帧。

官方地址:https://dev.mysql.com/doc/refman/8.4/en/window-functions-frames.html

要点速览
  • ROWS 按排序后的物理行位置取边界,1 PRECEDING 就是上一条行记录。
  • RANGE 按排序值取范围,并列值会一起进入当前帧;数值或时间偏移还要求匹配的 ORDER BY 类型。
  • 排查时显式写出 BETWEEN,同时检查 NULL、并列值和是否真的需要窗口帧。

先用并列日期复现两种窗口帧

假设一位客户在同一天支付了两笔订单,我们需要计算累计支付金额。下面的示例只展示查询结构,数据表名和金额均为本文原创示例:

SELECT
    customer_id,
    paid_at,
    amount,
    SUM(amount) OVER (
        PARTITION BY customer_id
        ORDER BY paid_at
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS total_by_rows,
    SUM(amount) OVER (
        PARTITION BY customer_id
        ORDER BY paid_at
        RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS total_by_range
FROM order_payment;
-- 注释:ROWS 按每条排序行推进,RANGE 会把当前 paid_at 的并列记录视为同一值组。

若三条数据的金额依次为 80、20、50,前两条 paid_at 相同,ROWS 的累计值会逐行变成 80、100;RANGE 在这两行上都可能得到 100。它不是重复计算,而是当前行的“值范围”同时覆盖了同值伙伴。

MySQL 窗口函数 ROWS 与 RANGE 在订单分区、支付时间排序和累计聚合之间的静态边界关系示意图
图1:MySQL 窗口帧边界操作示意图;左侧按物理行位置,右侧按 paid_at 排序值范围,说明并列值为何会形成不同累计结果。

按业务问题决定是物理行还是值范围

可以先把需求改写成一句判断:是“前面固定几条明细”,还是“前面某个数值/时间范围内的全部记录”?前者用 ROWS,后者才用 RANGE

需求建议帧边界含义常见坑
最近 3 条订单的移动平均ROWS BETWEEN 2 PRECEDING AND CURRENT ROW最多三条物理行排序相同仍会按行区分,最好补唯一键
过去 7 天的销售额RANGE INTERVAL 7 DAY PRECEDING按日期值向前取 7 天ORDER BY 应使用时间表达式
相同成绩并列名次后的累计占比谨慎使用 RANGE同值记录可共享当前帧不要误当成逐条明细累计

如果结果必须稳定到每一条订单,建议在排序中加入唯一的 payment_id,并明确使用 ROWS。不过,补唯一键只解决顺序稳定性,不能把“按值分组”的业务需求改成“按行计数”。

显式写帧,排查默认值和类型限制

ORDER BY 却不写帧时,MySQL 默认采用从分区开始到当前行、并包含当前排序值并列记录的范围,效果接近 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。这正是许多累计列在加上排序后突然变化的原因。

SELECT
    paid_at,
    amount,
    SUM(amount) OVER w AS running_amount,
    LAST_VALUE(amount) OVER w AS current_last_amount
FROM order_payment
WINDOW w AS (
    PARTITION BY customer_id
    ORDER BY paid_at, payment_id
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
);
-- 注释:显式 ROWS 让每条订单的边界清楚;payment_id 只用于稳定排序,不改变业务分组。

排查时按这个顺序看:第一,ORDER BY 是否存在并列值;第二,是否漏写帧而依赖默认值;第三,RANGE 的偏移是否与排序表达式类型匹配。比如数值偏移应配数值排序列,时间间隔应配时间排序列。NULL 排序值也要单独抽样,因为它们可能与同为 NULL 的记录形成一组。

MySQL 默认窗口帧与显式 ROWS 的分区排序唯一键和并列值边界关系示意图
图2:默认帧与显式 ROWS 的结果示意图;分组框标出分区、排序键和当前帧,帮助定位并列值与唯一键的边界。

用四个检查点确认结果不是误判

  1. 先只取一个 customer_id,按窗口排序列输出原始金额,确认是否真的有并列或 NULL
  2. 把窗口聚合临时改成 COUNT(*),观察每行帧内记录数量,先确认边界再看金额。
  3. 将默认帧改成显式 ROWSRANGE,对比差异;不要同时修改分区、过滤和聚合逻辑。
  4. 数据量较大时再查看执行计划和排序成本。窗口帧写对不等于一定走索引,过滤条件、分区键和排序键仍需结合实际计划判断。

MySQL 当前窗口帧只支持 ROWSRANGE;标准里的 GROUPSEXCLUDE 等写法不要直接迁移进生产查询。需要整分区统计的函数也不一定受帧影响,先确认函数本身是否使用当前帧。

常见问题

ROWS 和 RANGE 哪个性能更好?

不能只凭关键字判断。两者都可能需要排序或缓冲,实际成本取决于过滤、分区规模、排序列和执行计划;先固定同一份数据比较。

为什么 RANGE 的累计值在两行上相同?

因为两行的排序值相同,当前行的值范围把它们作为 peers 一起纳入了帧。若业务要逐条递增,改成显式 ROWS 并补唯一排序键。

ORDER BY 里加 payment_id 后,RANGE 还能按日期分组吗?

要谨慎。带偏移的 RANGE 需要与排序表达式匹配,额外的唯一键可能改变帧语义;按日期分组时应先确认 MySQL 对该窗口定义的类型和边界是否满足需求。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>