窗口函数比关联子查询快,因其基于一次排序扫描完成计算,时间复杂度约o(n log n),而关联子查询需对主表每行重复执行子查询,复杂度达o(n×m)。

为什么窗口函数能比关联子查询快得多
关联子查询在 MySQL 中常被写成 SELECT ..., (SELECT ... FROM t2 WHERE t2.id = t1.id) AS x FROM t1 这种形式。它的问题在于:对 t1 的每一行,都要重新执行一次子查询,时间复杂度接近 O(n × m)。而窗口函数(如 ROW_NUMBER()、RANK()、LAG()、SUM() OVER ())是基于排序后的一次扫描完成计算,底层使用内存缓冲区+有序遍历,复杂度接近 O(n log n),尤其当涉及分组聚合时优势更明显。
MySQL 8.0 才正式支持标准窗口函数,此前版本即使加索引也难优化这类“为每行查一次”的模式。
用 SUM() OVER (PARTITION BY ... ORDER BY ...) 替代按组累计求和的关联子查询
常见场景:统计每个用户从第一笔订单开始的累计消费金额。
错误写法(慢):
SELECT
user_id,
order_time,
amount,
(SELECT SUM(amount) FROM orders o2
WHERE o2.user_id = o1.user_id AND o2.order_time <p>正确替换(快):</p><pre class="brush:php;toolbar:false;">SELECT
user_id,
order_time,
amount,
SUM(amount) OVER (
PARTITION BY user_id
ORDER BY order_time, order_id
) AS cumsum
FROM orders;注意点:
-
PARTITION BY user_id对应原关联条件o2.user_id = o1.user_id -
ORDER BY order_time, order_id必须明确,否则累计顺序不确定;若order_time有重复,需加唯一列(如order_id)保序 - 不要漏掉
ORDER BY—— 没它,SUM() OVER (PARTITION BY ...)算的是组内总和,不是累计和
用 LAG() 或 LEAD() 替代“查上一行/下一行”的关联子查询
典型需求:计算同一用户相邻两笔订单的时间间隔(分钟)。
低效写法:
SELECT
user_id,
order_time,
(SELECT TIMESTAMPDIFF(MINUTE, o2.order_time, o1.order_time)
FROM orders o2
WHERE o2.user_id = o1.user_id
AND o2.order_time <p>高效写法:</p><pre class="brush:php;toolbar:false;">SELECT
user_id,
order_time,
TIMESTAMPDIFF(MINUTE,
LAG(order_time) OVER (PARTITION BY user_id ORDER BY order_time),
order_time
) AS gap_min
FROM orders;关键细节:
-
LAG()默认取前 1 行,也可写LAG(order_time, 2)取前两行 - 若首行无前驱数据,
LAG()返回NULL,可配合COALESCE(LAG(...), order_time)处理 -
PARTITION BY和ORDER BY必须严格匹配业务分组与时序逻辑,否则跨用户取值就错了
哪些情况不能直接用窗口函数替代?得留神
窗口函数不是万能解药,以下情形仍需谨慎或保留子查询:
- 子查询含复杂条件(如
WHERE status IN (SELECT ...)多层嵌套) - 需要关联多张表且无法合并到同一 FROM 子句(窗口函数只能作用于当前查询结果集)
- 要查“组内最大值对应的那一整行记录”,
MAX() OVER ()只给值,不带行上下文;此时得用ROW_NUMBER() OVER (... ORDER BY score DESC)+ 外层过滤rn = 1 - 窗口函数无法替代
EXISTS或NOT EXISTS这类存在性判断,它们语义不同,强行改写易出错
MySQL 8.0 的窗口函数性能虽好,但真正卡住效率的往往不是函数本身,而是 PARTITION BY 列没索引、ORDER BY 字段类型不一致(比如 DATETIME vs VARCHAR)、或者 OVER 子句里写了表达式导致无法利用排序缓存。这些细节比语法更影响实际效果。











