窗口函数性能优于自连接,因其仅需一次全表扫描、一次排序和一次临时表构建,而自连接会为外层每行重复触发排序与临时表操作,导致o(n²)复杂度;实测1000万行查最新订单,窗口函数耗时3.8秒,相关子查询达12.5秒。

窗口函数只扫一次表,自连接可能扫 N 次
核心区别在执行计划里:Using filesort 和 Using temporary 在窗口函数中各出现一次;而自连接中,这两个提示会随外层行数重复触发。比如外层 10 万行,内层就可能执行 10 万次排序和临时表构建。
常见错误现象:
- EXPLAIN 显示
Type: ALL且Rows列数值爆炸式增长 - 查询耗时随数据量非线性暴涨,10 万行就开始卡顿
- 执行计划里反复出现
DEPENDENT SUBQUERY
实操建议:
- 用
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_time DESC)替代NOT EXISTS或LEFT JOIN查最新订单 - 确保
PARTITION BY和ORDER BY字段有复合索引,例如(customer_id, order_time DESC) - 时间字段重复时,
ORDER BY order_time DESC, id DESC补二级排序,避免结果不可复现
LAG/LEAD 天然保序,自连接依赖物理连续性
有人写 JOIN t1 ON t2.id = t1.id + 1 做环比,但生产环境 ID 绝对不连续——删过数据、批量插入、分布式主键都会让这个假设崩掉。
实操建议:
- 改用
LAG(amount) OVER (PARTITION BY user_id ORDER BY event_time),它只认逻辑顺序,不依赖存储顺序 -
LAG(value, 1, 0)第三个参数设默认值,避免NULL污染后续计算(比如做减法得NULL) - 如果
event_time有重复,必须加二级排序字段(如id),否则 MySQL 8.0 每次返回“前一行”可能不同 -
LEAD()同理,但方向相反;两者都强制要求ORDER BY,否则报错ERROR 3589 (HY000): Window '<unnamed>' requires an ORDER BY clause</unnamed>
SUM() OVER 走累积算法,子查询每行触发独立扫描
传统写法 (SELECT SUM(amount) FROM orders o2 WHERE o2.date 是典型 O(N²) 模式:N 行数据,就要执行 N 次子查询扫描。
实操建议:
- 用
SUM(amount) OVER (ORDER BY date ROWS UNBOUNDED PRECEDING),MySQL 内部用单趟累积算法完成 - 必须显式写
ROWS,别省略——RANGE在时间字段上会把相同值的多行全纳入,导致“同一天多笔订单”被重复累加 - 日期字段含
NULL时,MySQL 默认把NULL排最前,可能让第一行累计值异常;建议提前WHERE date IS NOT NULL或用COALESCE(date, '1970-01-01')
窗口函数不能直接 WHERE 过滤,但嵌套开销几乎为零
你没法在 WHERE 里写 RANK() OVER () > 10,MySQL 会报语法错误。这不是缺陷,而是 SQL 执行顺序决定的:WHERE 在 SELECT 阶段之前,而窗口函数属于 SELECT 计算环节。
实操建议:
- 正确做法是套一层子查询或 CTE:
SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn FROM employees ) AS t1 WHERE rn
- 这个额外嵌套几乎不增加开销——窗口计算本身已产出全部结果,过滤只是最后切片
- 而自连接若想实现“每部门前 3 高薪”,往往得先聚合再关联,中间结果集更大、内存占用更高
真正容易被忽略的是:窗口函数快的前提是 PARTITION BY 和 ORDER BY 字段有合适索引。没索引时,MySQL 仍要回表排序,Using filesort 时间会暴涨——性能优势瞬间归零。











