rows between 完全依赖 order by 生成的逻辑行序,与磁盘存储顺序无关;必须用确定性排序(如 order by time, id)确保每行位置唯一稳定,否则窗口结果不可重现。

ROWS BETWEEN 的计算完全依赖 ORDER BY 排序后的物理行序,和磁盘存储顺序无关
很多人误以为 ROWS BETWEEN 是按表在磁盘上怎么存就怎么取,其实不是。它只认 ORDER BY 执行完后那一行一行排下来的“位置编号”——第1行、第2行……第n行。这个序列是逻辑生成的,跟聚簇索引、插入顺序、页分裂都没直接关系。
真正影响结果的,是 ORDER BY 列是否稳定、有无重复、是否覆盖所有行。比如:
- 用
ORDER BY created_at,但多条记录created_at相同 → 同一时间戳下的行序不确定,ROWS BETWEEN 1 PRECEDING AND CURRENT ROW可能每次执行都拉到不同前一行 - 用
ORDER BY id,但中间删过数据 → 行号连续,但业务含义断层,比如 id=100 的“前2行”可能是 id=98、99,跳过了本该存在的 97(如果它被删了) - 没写
ORDER BY→ 多数引擎(如 PostgreSQL)直接报错;MySQL 8.0+ 拒绝执行;SQL Server 可能跑通但结果不可重现
为什么不能靠表的“自然顺序”来推 ROWS BETWEEN 的行为
所谓“自然顺序”,在 SQL 标准里根本不存在。ANSI SQL 明确规定:不带 ORDER BY 的查询,返回行序是未定义的(undefined)。数据库可以按任何方式返回,包括内存缓存顺序、索引扫描路径、甚至并行线程调度顺序。
这意味着:
-
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW在没ORDER BY时,可能取到任意三行,且下次执行结果不同 - 即使你刚
INSERT完一批数据,看起来是“按插入顺序”,一旦发生 vacuum、reindex、主从同步延迟,顺序就可能变 - 分区表、分片集群中,“物理存储顺序”本身就没全局一致性,更不能作为计算依据
如何验证当前 ROWS BETWEEN 实际取的是哪几行
最可靠的方式,是把排序键和行号一起查出来,人工比对窗口范围:
SELECT
id,
created_at,
ROW_NUMBER() OVER (ORDER BY created_at, id) AS rn,
AVG(value) OVER (
ORDER BY created_at, id
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS ma3
FROM events;
重点看 rn 列:若某行 rn = 5,那它的窗口一定包含 rn IN (3,4,5) 的三行,不管它们 created_at 差多少、id 是否连续。
这个技巧能快速定位问题:
- 发现
rn跳变大段 → 说明ORDER BY字段有大量 NULL 或重复值,需加NULLS LAST或补唯一键 - 发现相同
created_at下rn每次执行不一致 → 说明缺少确定性二级排序,应追加id或event_id - 窗口结果明显偏移 → 先检查
rn是否真按你预期递增,而不是假设“应该如此”
真正危险的场景:ORDER BY 列存在 NULL 或高重复度
NULL 在多数数据库默认排最前(NULLS FIRST),但不同引擎行为不一致。一旦 ORDER BY created_at 里有几十个 NULL,ROWS BETWEEN 5 PRECEDING AND CURRENT ROW 就可能把一堆无效时间的数据全卷进来。
重复值更隐蔽:比如按 status(只有 0/1)排序,RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 会一次性拉入全部 status=0 的行;而 ROWS 虽然不“吞行”,但同一 status 下的行序仍不稳定,导致窗口边界漂移。
所以只要用 ROWS BETWEEN,就必须确保:ORDER BY 表达式能为每一行给出唯一、稳定、业务可解释的位置 —— 这往往意味着要拼接至少两个字段,例如 ORDER BY event_time, event_id。










