窗口函数比关联子查询快,是因为它仅需一次全表扫描、一次排序和一次线性遍历,时间复杂度约O(n log n);而关联子查询对主表每行都触发独立执行,导致N×M次扫描,复杂度常达O(n×m),EXPLAIN中反复出现DEPENDENT SUBQUERY即为典型特征。

窗口函数为什么比关联子查询快
窗口函数快,是因为它只扫描一次表、做一次排序、走一趟线性计算;而关联子查询(尤其是相关子查询)对主表每行都触发一次独立执行,实际是 N × M 次扫描。比如查每个员工的部门平均工资,(SELECT AVG(salary) FROM emp e2 WHERE e2.dept = e1.dept) 在 10 万行员工数据下,可能执行 10 万次子查询——每次都要过滤、聚合、返回单值。
- 窗口函数时间复杂度接近
O(n log n)(主要开销在排序) - 关联子查询时间复杂度常达
O(n × m),m 是子查询涉及的平均行数 - EXPLAIN 中若反复看到
DEPENDENT SUBQUERY或Using temporary; Using filesort,基本就是关联子查询在拖慢整条 SQL
哪些关联子查询能被窗口函数直接替换
不是所有都能换,但高频场景基本覆盖:- 标量子查询:如
(SELECT AVG(salary) FROM emp e2 WHERE e2.dept = e1.dept)→ 改用AVG(salary) OVER (PARTITION BY dept) - 排名类:如
(SELECT COUNT(*) + 1 FROM emp e2 WHERE e2.salary > e1.salary)→ 改用RANK() OVER (ORDER BY salary DESC) - 累计求和:如
(SELECT SUM(amount) FROM orders o2 WHERE o2.user_id = o1.user_id AND o2.time <= o1.time)→ 改用SUM(amount) OVER (PARTITION BY user_id ORDER BY time, id) - 查最新/最旧记录:如用
NOT EXISTS或LEFT JOIN找每个用户的最新订单 → 改用ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC, id DESC),再外层WHERE rn = 1
注意:替换时必须保证 PARTITION BY 和原子查询的 WHERE 条件语义一致,包括 NULL 处理(例如 dept 为 NULL 时,PARTITION BY dept 会把所有 NULL 归为一组,而 e2.dept = e1.dept 对 NULL 比较结果为 UNKNOWN,不匹配——需提前用 COALESCE(dept, 'NULL_GROUP') 对齐)
窗口函数容易踩的坑
写得不对,性能可能比子查询还差:- 忘写
ORDER BY:比如SUM(amount) OVER (PARTITION BY user_id)算的是组内总和,不是累计和;排名函数没ORDER BY直接报错ERROR 3589 -
ORDER BY字段无索引:当PARTITION BY a ORDER BY b中b没索引,数据库只能磁盘排序,速度骤降 - 时间字段重复时没加二级排序:如
ORDER BY created_at DESC遇到同秒多单,ROW_NUMBER()每次结果可能不同,必须补, id DESC - 误用
RANGE帧:比如SUM(x) OVER (ORDER BY date RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),若同一天有多行,会把当天所有行全累加进去;应改用ROWS - 直接在
WHERE里用窗口函数:语法不允许,WHERE RANK() OVER (...) > 1报错,必须套一层子查询或 CTE
别忽略数据库版本和索引配合
窗口函数不是“开了就快”,它依赖底层执行引擎和物理结构:- MySQL 8.0+、PostgreSQL 8.4+、SQL Server 2005+、SQLite 3.25+ 才支持;旧版硬写会报错
FUNCTION xxx does not exist -
PARTITION BY a ORDER BY b最好有复合索引(a, b),否则排序开销吃掉全部优势 - PostgreSQL 中,窗口函数可参与并行执行(需
max_parallel_workers_per_gather > 0),而关联子查询常强制串行 - MySQL 8.0 默认关并行,但即使单线程,窗口函数的内存缓冲+有序遍历也比反复触发子查询稳定得多
真正卡住的往往不是语法会不会写,而是 PARTITION BY 和 ORDER BY 背后有没有对应索引,以及 NULL 和重复值是否被显式处理。


















