窗口函数比关联子查询快,因其仅单次扫描表并线性遍历计算,时间复杂度约O(N log N);而关联子查询为嵌套循环,最坏达O(N×M),且每行重执行子查询,易触发磁盘排序与临时表。

因为窗口函数只扫描一次表,而关联子查询对主表每行都重复执行子查询——这是本质区别,不是“写法优化”,是算法复杂度的降维打击。
关联子查询为什么慢到不可接受
它本质是嵌套循环:主表 N 行 × 子查询 M 行 = 最坏 O(N×M) 复杂度。数据库无法提前预判子查询结果是否可复用,只能老老实实每行都重跑一遍。
- 常见错误现象:
EXPLAIN显示DEPENDENT SUBQUERY(MySQL)或Correlated Subplan(PostgreSQL),且rows列数值是表总行数的几倍甚至几十倍 - 哪怕子查询字段有索引,优化器也常放弃重写为 JOIN,尤其当子查询含
AVG()、DISTINCT或多层嵌套时 - 执行计划里频繁出现
Using temporary; Using filesort,说明大量计算被迫落盘
窗口函数快在哪儿:单次排序 + 线性遍历
底层机制是:先按 PARTITION BY 分组、再按 ORDER BY 排序,最后仅需一次有序扫描,在内存缓冲区中滑动计算——时间复杂度约 O(N log N)。
-
ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC):排序后从头到尾扫一遍,给每行打序号 -
SUM(amount) OVER (PARTITION BY user_id ORDER BY order_time):每个用户组内边扫边累加,不回溯 - 避免了磁盘随机 IO 和中间结果集膨胀,PostgreSQL 甚至能自动并行化该过程(需开启
max_parallel_workers_per_gather)
别以为写了窗口函数就一定快:三个关键陷阱
写错 PARTITION BY、漏掉 ORDER BY 或误用 RANGE BETWEEN,性能可能比原关联子查询还差。
-
PARTITION BY dept_id必须和原子查询的WHERE e2.dept_id = e1.dept_id语义完全一致;若dept_id含NULL,得提前用COALESCE(dept_id, -1)对齐,否则分组行为不等价 -
LAG()、SUM() OVER ()类函数必须带ORDER BY,否则报错或结果不确定;只写ORDER BY created_at不够,重复时间戳会导致窗口内顺序随机,应补唯一列:ORDER BY created_at, id - 想算“过去 7 天销售额”,别用
RANGE BETWEEN INTERVAL '7 days' PRECEDING——它要为每行重新匹配值范围,无法走索引;改用ROWS BETWEEN 6 PRECEDING AND CURRENT ROW+ 预处理sale_date::date字段更稳
真正卡住性能的往往不是函数本身,而是 PARTITION BY 和 ORDER BY 搭配的物理代价:没索引的排序字段会强制磁盘排序,比原 JOIN 还慢。动手前先看 EXPLAIN 里有没有 Sort 节点,再决定要不要建复合索引。

















