窗口函数比子查询更高效,因其仅需一次排序和一次线性遍历,在内存中构建并复用窗口框架;而子查询每行都重复建临时表、反复排序,导致O(N²)复杂度与内存峰值线性增长。
窗口函数的 Using temporary 是构建内存中的窗口框架,整张表的数据只进这个结构一次;而关联子查询在 DEPENDENT SUBQUERY 模式下,每处理外层一行,就重新分配一块临时空间、重建筛选条件、再扫描内表——10 万行外层数据,就可能触发 10 万次临时结构创建。MySQL 不会复用这些临时结构,内存峰值直接线性翻倍。
窗口函数的 Using filesort 发生在初始化阶段:按 PARTITION BY + ORDER BY 一次性排序,后续所有窗口计算都基于这个已排序流滑动执行;子查询里如果带 ORDER BY 或隐含排序(比如 MAX() 配合 WHERE),每次执行都要重排——没索引时,就是 N 次磁盘排序,内存缓冲区反复被冲刷。
(department, salary DESC) 时,窗口函数可跳过排序,子查询仍需为每组单独判断是否走索引LAG()、LEAD() 这类函数依赖排序定义“前一行”,没 ORDER BY 就退化为随机行,但子查询无法规避该逻辑开销窗口函数全程在 sort_buffer 和 window_frame 内存区域中流式计算,只要总数据量不超过 sort_buffer_size 和 read_rnd_buffer_size 之和,就不会写磁盘;子查询单次结果集稍大(比如部门平均薪资涉及几百人),或内存不足时,就会生成磁盘临时表——这不仅吃内存,还引入 I/O 延迟和锁竞争。
tmp_table_size 和 max_heap_table_size 通常为 64MB,超限即落盘loops 值在 EXPLAIN ANALYZE 中等于外层行数,是内存压力最直接的指标SUM() OVER (PARTITION BY user_id ORDER BY order_time) 在内存中维护一个滑动累加器,同一用户的数据连续到达时,只需加减当前值;子查询如 (SELECT SUM(amount) FROM orders o2 WHERE o2.user_id = o1.user_id) 每次都要从头扫描、过滤、累加,无法复用前序计算结果,CPU 和内存带宽都被重复消耗。
真正容易被忽略的是:窗口函数的内存节省不是靠“少算”,而是靠“不重复建上下文”——一旦你看到执行计划里 Using filesort 和 Using temporary 同时出现且 loops=1,基本可以确认这是最优路径;若子查询的 loops 显著大于 1,内存压力已经实打实地来了。