窗口函数通常比自连接快,因其仅需一次全表扫描、一次排序和一次临时结构构建,时间复杂度约o(n log n);而自连接对主表每行都触发副表扫描,易退化为o(n²)。

窗口函数性能更好,但前提是写法正确、索引匹配、场景合适。盲目替换反而可能更慢。
为什么窗口函数通常比自连接快?
核心区别在执行路径:窗口函数只扫一次表、排一次序、建一次临时结构;自连接对主表每行都触发一次副表扫描,容易变成 O(N²)。
常见错误现象:
EXPLAIN 显示
Type: ALL 且
Rows 列爆炸式增长
执行计划反复出现
DEPENDENT SUBQUERY 或
Nested Loop
查询耗时随数据量非线性暴涨,10 万行就开始卡顿
实操建议:
确保
PARTITION BY 和
ORDER BY 字段有复合索引,例如
(customer_id, order_time DESC)
时间字段重复时,补二级排序字段,如
ORDER BY order_time DESC, id DESC
别省略
ROWS 关键字——用
RANGE 在时间字段上会把同一天多笔订单全纳入,导致重复累加
哪些自连接能直接换?
不是所有都能换,关键看是否满足「单表、分组、行间计算」三要素:
查最新/最早记录 → 用
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC)
算相邻差值(环比、登录间隔)→ 用
LAG(amount) OVER (PARTITION BY user_id ORDER BY event_time)
滚动窗口统计(7天累计)→ 用
SUM(amount) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)
组内条件计数(薪资高于本部门平均的人数)→ 用
COUNT(CASE WHEN salary > AVG(salary) OVER (PARTITION BY dept_id) THEN 1 END) OVER (PARTITION BY dept_id)
注意:
LAG() 和
LEAD() 必须带
ORDER BY,否则 MySQL 8.0+ 报错
ERROR 3589 (HY000): Window '<unnamed>' requires an ORDER BY clause</unnamed>
为什么换了反而更慢?
窗口函数不是银弹,性能倒退往往因为:
漏写
PARTITION BY,导致全表排序,比带索引的自连接还重
原自连接本身过滤极窄(如
WHERE user_id = 123 后再 JOIN),而窗口函数被迫处理全量数据
用了
RANGE BETWEEN INTERVAL '7 days' PRECEDING,数据库无法利用索引,每行都要重新扫描匹配范围
内存不足时,MySQL 把窗口排序刷到磁盘,IO 成瓶颈;而索引驱动的嵌套循环 JOIN 可能更快
验证方法:对比
EXPLAIN 里的
Sort 和
Nested Loop 成本,别光看代码行数
ORDER BY 是语义必需,不是可选语法糖
很多翻车都源于空着
ORDER BY:
ROW_NUMBER() OVER (PARTITION BY dept) 在 PostgreSQL 直接报错,在 MySQL 8.0+ 默认按物理存储顺序排——但删过数据、批量插入、分布式主键都会让这个顺序不可靠
时间戳精度不够(如只有秒级)时,必须补唯一字段:
ORDER BY created_at DESC, id DESC
LAG(value, 1, 0) 第三个参数设默认值,避免 NULL 污染后续计算(比如做减法得 NULL)
真正要的是确定性排序:时间戳、ID、业务主键都行,但必须显式写出。如果真没自然排序字段,至少加个
ORDER BY id,别空着。