MySQL 8.0 窗口函数对比子查询有什么性能优势?

文章导读
MySQL 8.0 窗口函数通过单次扫描和流式计算替代子查询的嵌套循环,显著降低 I/O 和 CPU 开销,适合排名、累计求和及环比分析场景。注意窗口函数无法直接在 WHERE 子句中使用,且需确保分区和排序字段建有索引以避免文件排序开销。
📋 目录
  1. A 命令速用版
  2. B 为什么会这样
  3. C 分步处理
  4. D 怎么验证是否生效
  5. E 常见坑
  6. F 常见问题
  7. G 参考来源
A A

MySQL 8.0 窗口函数通过单次扫描和流式计算替代子查询的嵌套循环,显著降低 I/O 和 CPU 开销,适合排名、累计求和及环比分析场景。注意窗口函数无法直接在 WHERE 子句中使用,且需确保分区和排序字段建有索引以避免文件排序开销。

先说结论:窗口函数将复杂度从 O(N²) 降至 O(N log N),核心优势在于减少表扫描次数和临时表构建。

  • 先定位:检查 EXPLAIN 输出中是否出现 DEPENDENT SUBQUERY 或 loops 值过大。
  • 先做:将关联子查询改写为 ROW_NUMBER()、SUM() OVER() 或 LAG() 等窗口函数。
  • 再验证:对比改写前后的 actual time 和 Using filesort 触发次数。

命令速用版

以下是子查询改写为窗口函数的典型代码对比,直接替换可提升执行效率。

场景 1:累计求和
子查询写法(慢):SELECT user_id, (SELECT SUM(amount) FROM orders o2 WHERE o2.user_id = o1.user_id AND o2.order_time <= o1.order_time) AS cumsum FROM orders o1;
窗口函数写法(快):SELECT user_id, SUM(amount) OVER (PARTITION BY user_id ORDER BY order_time) AS cumsum FROM orders;

场景 2:获取前 N 条记录
子查询写法(慢):SELECT * FROM employees e1 WHERE (SELECT COUNT(*) FROM employees e2 WHERE e2.dept = e1.dept AND e2.salary > e1.salary) < 3;
窗口函数写法(快):SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn FROM employees) t WHERE rn <= 3;

MySQL 8.0 窗口函数对比子查询有什么性能优势?

为什么会这样

窗口函数比子查询快的根本原因是执行引擎仅需一次排序和一次线性遍历,避免重复读盘与反复建临时结构。

关联子查询在 MySQL 中常被写成对外层每一行都重新执行一次子查询的形式,时间复杂度接近 O(N × M)。而窗口函数基于排序后的一次扫描完成计算,底层使用内存缓冲区加有序遍历,复杂度接近 O(N log N)。执行计划里出现 Using filesort 和 Using temporary 不代表出错,这是 MySQL 正在为窗口准备分区和排序上下文,关键区别在于 Using filesort 只发生一次,而子查询里的 ORDER BY 可能在每个相关子查询中重复触发。

分步处理

优化过程分为版本确认、语法改写和索引配合三步,确保改写后性能稳定。

步骤 1:确认版本支持
窗口函数是 MySQL 8.0 正式引入的特性,执行 SELECT VERSION(); 确认版本号不低于 8.0.0。此前版本即使加索引也难优化这类“为每行查一次”的模式。

步骤 2:改写 SQL 逻辑
识别查询中的关联子查询或自连接,将其替换为对应的窗口函数。例如计算环比使用 LAG(amount) OVER (ORDER BY date) 替代 LEFT JOIN 时间偏移条件。注意不要漏掉 ORDER BY,没它 SUM() OVER (PARTITION BY) 算的是组内总和而不是累计和。

MySQL 8.0 窗口函数对比子查询有什么性能优势?

步骤 3:配合索引优化
确保 PARTITION BY 和 ORDER BY 字段建有复合索引,例如 (department, salary DESC)。字段顺序不能反,否则分区失效。降序索引在 MySQL 8.0+ 才真正支持,建索引时要显式写 DESC。

怎么验证是否生效

使用 EXPLAIN ANALYZE 查看实际执行时间和 loops 次数,确认扫描次数减少。

执行 EXPLAIN ANALYZE 你的 SQL 语句,观察输出中的 actual time 和 loops 值。窗口函数的 actual time 通常集中在 1–2 个节点上,而子查询的耗时会分散在多个 SELECT 层级,且 loops 值常远大于 1。若看到 Using filesort 和 Using temporary 各出现一次,属于正常准备窗口上下文;若这两个提示随外层行数重复触发,则说明仍在使用子查询逻辑。

常见坑

窗口函数不能直接用于 WHERE 子句过滤,需包裹子查询,且索引配合不当会导致性能下降。

MySQL 8.0 窗口函数对比子查询有什么性能优势?

你没法在 WHERE 里写 RANK() OVER () > 10,MySQL 会报语法错误,这不是缺陷而是 SQL 逻辑执行顺序决定的,正确做法是套一层子查询或 CTE。别以为用了窗口函数就万事大吉,如果没建复合索引,MySQL 仍要回表排序,Using filesort 时间会暴涨。时间字段重复常见,单靠 ORDER BY order_time 不稳,建议补上唯一列如 order_id 保序。

常见问题

窗口函数在哪个 MySQL 版本可用?

MySQL 8.0 及以上版本正式支持标准窗口函数,5.7 及以下版本不支持。

为什么改写后 EXPLAIN 还有 Using filesort?

这是 MySQL 正在为窗口准备分区和排序上下文,只要只发生一次且配合了索引,属于正常现象。

窗口函数能替代所有子查询吗?

不能,窗口函数无法直接在 WHERE 子句中使用,且不适用于需要大幅减少结果行数的聚合过滤场景。

参考来源

  • MySQL 8.0 的窗口函数对比自连接查询在性能上有何优势
  • MySQL 8.0 如何使用窗口函数替代关联子查询_大幅提升查询效率
  • 为什么 MySQL 8.0 窗口函数比子查询更高效_分析窗口函数执行计划
  • MySQL 窗口函数:比子查询更高效的 5 种场景