窗口函数比关联子查询快的根本原因是执行引擎仅需一次排序和一次线性遍历,避免重复读盘与反复建临时结构;using filesort和using temporary是正常准备窗口上下文,并非错误。

RANK()、SUM() OVER 这类窗口函数比等价的关联子查询快,根本原因不是“语法更高级”,而是执行引擎在内存中完成了一次排序 + 一次线性遍历,避免了重复读盘和反复建临时结构。
执行计划里出现 Using filesort 和 Using temporary 不代表出错
很多人看到 EXPLAIN 输出里有 Using filesort 就以为排序慢、写法错了——其实这是 MySQL 正在为窗口准备分区和排序上下文。
-
Using filesort只发生一次:全表或按索引顺序扫一遍,之后所有窗口计算都基于这个已排序流 -
Using temporary是构建窗口框架用的内存临时结构,数据只进一次;而关联子查询可能为每一行都新建一个临时表 - 对比子查询的
DEPENDENT SUBQUERY,它的loops值常等于外层行数(比如 10 万行 → 执行 10 万次内层查询)
窗口函数真正耗时的只有排序阶段
窗口函数本身不“计算慢”,它只是在线性遍历中做简单累加或比较。真正开销集中在 PARTITION BY 和 ORDER BY 的组合排序上。
- 没索引时,MySQL 得为每个分组单独排序(比如 100 个部门 → 排 100 次)
- 有复合索引
(department, salary DESC),就能跳过大部分排序,直接按索引顺序流式处理 -
ORDER BY salary DESC必须配INDEX(salary DESC)(MySQL 8.0+ 支持),仅INDEX(salary)无法利用降序
为什么子查询容易触发磁盘临时表
关联子查询每执行一次,都可能重新走一遍 WHERE 条件、重新扫描、重新排序。当单次子查询结果集稍大,或内存不足时,就会落到磁盘临时表。
- 例如查每个用户累计消费:
(SELECT SUM(amount) FROM orders o2 WHERE o2.user_id = o1.user_id AND o2.order_time - 优化器无法提前预判每组数据量,只能保守地为每次子查询分配独立资源
- 而窗口函数的
SUM() OVER (PARTITION BY user_id ORDER BY order_time)共享同一块排序缓冲区,全程在内存中滑动计算
别忽略 ORDER BY 的隐含要求
像 SUM() OVER (PARTITION BY x) 看似没排序,但实际算的是组内总和,不是累计和;要累计,必须显式写 ORDER BY,否则结果不可靠。
- 时间字段重复很常见,
ORDER BY order_time不够稳,得补上唯一列如order_id -
LAG()和LEAD()同样依赖ORDER BY定义“前一行”是谁,没它就无意义 - 窗口函数不能用于
WHERE子句过滤(比如不能直接写WHERE RANK() OVER (...) ),得套 CTE 或子查询再筛
PARTITION BY 和 ORDER BY 的索引顺序,或者误以为没 ORDER BY 也能做累积计算。











