窗口函数是单次扫描+流式计算,子查询是多次扫描+嵌套执行、复杂度O(N²);EXPLAIN ANALYZE中Using filesort和Using temporary是正常准备排序与分区上下文,非错误。

窗口函数不是语法糖,它是单次扫描+流式计算的执行模型;子查询是多次扫描+嵌套执行,天然有O(N²)复杂度。
窗口函数执行计划里为什么总看到Using filesort和Using temporary
这两个提示不是“出错了”,而是MySQL在告诉你:它正在为窗口准备排序和分区上下文。关键区别在于——Using filesort只发生一次(全表或按索引顺序扫一遍),而子查询里的ORDER BY可能在每个相关子查询中重复触发。同样,Using temporary用于构建窗口框架,但数据只进临时结构一次;子查询则可能为每个外层行反复建临时表、反复排序。
实际观察EXPLAIN ANALYZE输出时,窗口函数的actual time通常集中在1–2个节点上;子查询的耗时会分散在多个SELECT层级,且loops值常远大于1(比如外层1000行,内层子查询就执行1000次)。
子查询的执行次数怎么爆增的
看一个典型场景:查每个员工在部门内的薪资排名,用子查询写就是:
SELECT e1.name, e1.salary, (SELECT COUNT(*) + 1 FROM employees e2 WHERE e2.department = e1.department AND e2.salary > e1.salary) AS rank FROM employees e1;
这个语句的实际行为是:
- 外层扫描
employees表N行 - 对每一行
e1,都执行一次内层SELECT,即N次独立查询 - 每次内层查询都要全表/索引扫描找符合条件的
e2行 - 若没覆盖索引,还可能触发N次
Using filesort
而等价的窗口写法:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
SELECT name, salary, RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rank FROM employees;
只需要一次全表扫描(或走idx_department_salary索引),分区+排序+函数计算全部在内存流水线中完成。
哪些索引能真正加速窗口函数
窗口函数不靠“避免排序”来提速,而是靠“让排序更快”。所以有效索引必须匹配PARTITION BY和ORDER BY的组合顺序:
- 如果写
PARTITION BY dept_id ORDER BY hire_date,索引应为(dept_id, hire_date),不能反过来 -
ORDER BY salary DESC需要INDEX(salary DESC)(MySQL 8.0+支持降序索引) - 只建
INDEX(dept_id)没用——它能加速分区定位,但每个分区内仍要单独排序 - 复合索引中,
PARTITION BY字段必须在前,ORDER BY字段紧随其后,中间不能插其他列
没合适索引时,EXPLAIN会显示Using filesort出现在最外层;有索引后,该提示可能消失,或仅剩Using temporary(说明分区已优化,只剩框架管理开销)。
为什么EXPLAIN ANALYZE比EXPLAIN更值得依赖
EXPLAIN只做估算,常把窗口函数“猜”成高成本操作;EXPLAIN ANALYZE真实执行后会暴露关键事实:
- 实际
rows数是否远小于估算值?说明统计信息过期,需ANALYZE TABLE -
actual time是否集中在Sort或Window function节点?确认瓶颈位置 - 有没有意外的
Nested loop?那可能是窗口函数被错误地和JOIN混用,导致笛卡尔积放大 -
loops值是否等于外层结果行数?如果是,说明窗口逻辑没被下推,可能因WHERE条件写在窗口之后
最容易被忽略的一点:窗口函数的计算是在所有WHERE过滤之后才进行的。如果你在OVER()里用了未被过滤字段,或者把WHERE写在窗口别名之后(比如HAVING误用),执行计划会退化——这时候EXPLAIN ANALYZE的loops暴涨就是最直接的证据。

















