窗口函数执行效率更高是因为其单次扫描+流式计算模型将复杂度从O(N²)降至O(N log N),而非仅因语法简洁;它通过一次Using filesort和Using temporary构建共享上下文,避免子查询的重复触发。

窗口函数不是“写得更短所以更快”,而是执行模型根本不同:它靠单次扫描 + 流式计算完成原本需要 N 次独立子查询的任务,复杂度从 O(N²) 降到 O(N log N) 或更低。
执行计划里只出现一次 Using filesort 和 Using temporary
这两个提示在窗口函数的 EXPLAIN 中是正常现象,代表 MySQL 正在构建分区和排序上下文。关键在于——它们各只出现一次。而等价的子查询(比如查每个员工部门内排名)会让 Using filesort 和 Using temporary 随外层行数重复触发。10 万行外层数据,就可能引发 10 万次排序和临时表构建。
- 子查询的
EXPLAIN ANALYZE里,loops值常远大于 1,耗时分散在多层SELECT - 窗口函数的耗时集中在 1–2 个节点,
WINDOW操作是原子性的 - 别被 “Using temporary” 吓到——窗口用的临时结构是共享的,子查询建的是隔离的、重复的
ROW_NUMBER() 取最新记录 vs 自连接 NOT EXISTS
查“每个用户的最新订单”,传统写法容易退化成嵌套循环(Type: ALL),尤其当时间字段无索引或有大量重复值时。窗口函数把逻辑压进一次排序流中,再靠外层过滤切片。
- 必须带
PARTITION BY user_id,否则变成全表编号,业务语义失效 -
ORDER BY create_time DESC, order_id DESC是保序刚需:时间相同时靠主键兜底,避免结果不可复现 - 漏掉外层
WHERE rn = 1就白写了——窗口函数不减少行数,只是打标 - 实测 1000 万行表,自连接写法耗时 12.5 秒,
ROW_NUMBER()写法 3.8 秒
LAG/LEAD 替代 JOIN t1 ON t2.id = t1.id + 1
ID 连续是开发幻想,生产环境 ID 绝对不连续。用 ID 做关联,删过数据、批量导入、分库分表都会让结果错位甚至断裂。而 LAG() 只认 ORDER BY 定义的逻辑顺序,天然抗干扰。
-
LAG(amount, 1, 0)第三个参数是默认值,避免 NULL 导致后续计算中断(如amount - LAG(amount)) - 如果
ORDER BY event_time有重复,且没加二级排序(如id),MySQL 8.0 可能每次返回不同“前一行” -
LEAD()同理,方向相反;两者都强制要求ORDER BY,否则报错ERROR 3589 - 日期字段做偏移(如
s1.date = s2.date + INTERVAL 1 DAY)极易因某天缺数据断链,LAG()不依赖物理存在
索引没配对,窗口函数一样慢
用了窗口函数不等于自动走索引。排序成本压倒算法优势时,它比小结果集的子查询还慢。真正卡住的地方往往不是函数本身,而是 ORDER BY 没走索引,或者误加了 PARTITION BY 把全局序号切碎了。
- 复合索引必须严格匹配:
PARTITION BY dept_id ORDER BY salary DESC→ 索引要建为(dept_id, salary DESC) - 字段顺序不能反:先
PARTITION BY列,再ORDER BY列,中间不能插其他字段 -
ORDER BY DATE(created_at)这类表达式会让索引完全失效,必须改用created_at原字段 - 跑之前一定看
EXPLAIN FORMAT=TREE,确认有没有WINDOW节点和异常高的rows估算
窗口函数快,但快得有前提:版本 ≥ 8.0、OVER() 子句写对、索引跟上、外层过滤套好。最常被忽略的,是把 PARTITION BY 当成可有可无的装饰,或者以为 WHERE 里能直接写窗口函数结果。



















