窗口函数执行效率更高,因其仅需一次全表扫描、一次排序和一次临时表构建;而自连接或关联子查询会为外层每行重复触发排序与临时表操作,导致o(n²)复杂度。

窗口函数只扫一次表,自连接要扫 N 次
核心区别在执行计划层面:窗口函数的 EXPLAIN 里 Using filesort 和 Using temporary 各出现一次;而自连接中,这两个提示会随外层行数重复触发。比如外层 10 万行,内层就可能执行 10 万次排序和临时表构建,复杂度从 O(N) 变成 O(N²)。
实测数据佐证:在 1000 万行 orders 表中查“每个客户的最新订单”,相关子查询(本质是隐式自连接)耗时 12.5 秒;改用 ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_time DESC) 后降到 3.8 秒——差了三倍多。
LAG/LEAD 替代自连接做环比,代码和性能双降维
计算昨日销售额、上一条记录差值这类需求,传统写法依赖 LEFT JOIN + 时间偏移条件,极易因时间字段重复或缺失导致结果错位;窗口函数直接用 LAG(amount) OVER (ORDER BY date),逻辑干净且天然保序。
- 自连接必须确保连接条件严格匹配(如
s1.date = s2.date + INTERVAL 1 DAY),一旦某天无数据就断链 -
LAG不依赖物理存在,空值自动补NULL,语义更准确 - 索引友好:只要
ORDER BY字段有索引,LAG就能走索引扫描;自连接的日期运算常使索引失效
窗口函数不能用 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 <p>这个额外嵌套几乎不增加开销——窗口计算本身已产出全部结果,过滤只是最后切片。而自连接若想实现“每部门前 3 高薪”,往往得先聚合再关联,中间结果集更大、内存占用更高。</p><h3>索引配合不当,窗口函数也会变慢</h3><p>别以为用了窗口函数就万事大吉。如果 <code>PARTITION BY department ORDER BY salary DESC</code> 没建复合索引 <code>(department, salary DESC)</code>,MySQL 仍要回表排序,<code>Using filesort</code> 时间会暴涨。</p>
- 字段顺序不能反:必须先
department再salary,否则分区失效 - 降序索引在 MySQL 8.0+ 才真正支持,建索引时要显式写
DESC - 用表达式排序(如
ORDER BY DATE(created_at))会让索引完全失效 - 时间字段重复常见,单靠
ORDER BY order_time不稳,建议补上唯一列如order_id保证确定性
真正卡住性能的,往往不是窗口函数本身,而是没意识到它对索引结构的强依赖。











