order by 是窗口函数中定义行序的唯一依据,缺失则导致row_number()等结果不确定、last_value()只返回当前行;它激活窗口帧机制,影响sum累计逻辑,并决定rows/range行为差异及性能表现。

ORDER BY 不只是排序,它定义了窗口的“行序生命线”
不加 ORDER BY 的窗口函数(如 SUM() OVER (PARTITION BY x))默认对整个分区做静态聚合,结果每行都一样;一旦加上 ORDER BY,数据库就必须按指定顺序逐行推进计算——这直接激活了窗口帧(frame)机制,而帧的起点和终点(比如 CURRENT ROW)全是基于这个顺序定义的。
常见错误现象:ROW_NUMBER() OVER (PARTITION BY user_id) 没写 ORDER BY,每次执行序号乱跳;SUM(amount) OVER (ORDER BY ts) 返回值随查询时间变化,不是固定总和。
-
ORDER BY是窗口内“行位置”的唯一依据,没有它,LAG()、LEAD()、FIRST_VALUE()都无法定位“上一行”或“首行” - 即使只用
PARTITION BY,ORDER BY缺失时,LAST_VALUE()也永远只返回当前行——因为默认帧是RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,CURRENT ROW在无序状态下含义模糊 - 排序字段有重复值时,
RANGE帧会把所有同值行视为“同一位置”,导致SUM() OVER (ORDER BY date RANGE ...)把某天全部销量一次性加总,跳过中间行
ROWS 和 RANGE 的区别不是语法差异,而是计算逻辑分水岭
ROWS 按物理行号计数,RANGE 按排序值的逻辑区间划分。两者在 ORDER BY 存在重复值时行为完全不同,直接影响累计类函数结果。
典型场景:销售表按 sale_date 排序,某天有 3 条记录。
-
SUM(sales) OVER (ORDER BY sale_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW):逐条累加,第 1 行=第 1 条销量,第 2 行=前 2 条和,第 3 行=前 3 条和 -
SUM(sales) OVER (ORDER BY sale_date RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW):第 1 行就等于当天全部 3 条销量之和,因为所有同日期记录被归入同一RANGE组 -
LAG(sales, 1) OVER (ORDER BY sale_date)必须用ROWS,RANGE下“上一行”可能不存在或指向同组任意行
ORDER BY 字段没索引?性能会断崖式下跌
窗口函数带 ORDER BY + ROWS BETWEEN(如滚动平均、累计求和)时,数据库必须先完成全分区内部排序,再逐行划窗。若缺少对应索引,就会触发磁盘级 filesort,I/O 成瓶颈。
- 复合索引必须严格按
PARTITION BY字段在前、ORDER BY字段紧随其后的顺序建立,例如(user_id, created_at);(created_at, user_id)无效 -
ORDER BY DATE(created_at)这类表达式会让普通索引失效,MySQL 8.0+ 才支持函数索引,否则只能冗余存储日期字段再建索引 - 执行计划中
EXPLAIN出现Using filesort就是危险信号,说明排序没走索引
LAST_VALUE() 总返回当前行?问题出在默认帧,不是函数本身
LAST_VALUE(x) OVER (ORDER BY ts) 看似想取分区末值,实际永远返回当前行的 x——因为默认窗口帧是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,“最后”仅限于当前行及之前,不是整个分区。
- 要取整个分区最后一行的值:
LAST_VALUE(x) OVER (PARTITION BY group_id ORDER BY ts ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 要取排序后逻辑末位(可能多行并列),仍需
ROWS+UNBOUNDED FOLLOWING;RANGE在重复值下会把末位扩展成一个值区间,结果不可控 -
FIRST_VALUE()同理,不显式指定帧时,在RANGE下也不保证返回物理首行
ORDER BY 的真正作用,从来不是“让结果好看一点”,而是为每一行标定坐标。这个坐标一旦缺失或模糊,后续所有基于位置的操作(累计、偏移、首尾提取)都会失去确定性。最易忽略的点是:哪怕你只想要一个静态分区最大值,只要写了 ORDER BY,就得同步考虑帧定义和索引匹配——否则不是结果错,就是慢到不可用。











