bigywater.tech
MySQL / InnoDB · 41

窗口函数执行计划

同一个窗口函数查询,只改外层ORDER BY的写法,看执行计划里Sort出现几次,以及'看起来一样'的排序条件是否真的能省掉一次排序。

2次排序
外层ORDER BY哪怕和窗口子句写得一样,还是要再排一次
197ms vs 96ms
真不一样的ORDER BY比看似一样的ORDER BY慢一倍多

做的事:同一个窗口函数查询,加不同的外层ORDER BY

在5万行orders上按customer_id分组,用created_at排序算累计金额,只改最外层的ORDER BY子句。

代码SELECT customer_id, created_at, amount, SUM(amount) OVER (PARTITION BY customer_id ORDER BY created_at) AS running_total FROM orders -- 分别测试:不加ORDER BY / ORDER BY customer_id, created_at(和窗口子句完全一致)/ ORDER BY amount DESC(不一致)

验证:不加ORDER BY,只有窗口函数自己需要的那一次排序

实测 · 本机EXPLAIN FORMAT=TREE输出
-> Window aggregate with buffering: sum(orders.amount) OVER (PARTITION BY orders.customer_id ORDER BY orders.created_at)
    -> Sort: orders.customer_id, orders.created_at        ← 只有这一次排序
        -> Index range scan on orders using idx_cust ...

窗口函数要按PARTITION BY customer_id ORDER BY created_at计算累计值,必须先把数据按这个顺序排好,这一次排序无法省略。这时候整条查询里只有这一次Sort。

验证:外层ORDER BY就算写得和窗口子句一模一样,还是要再排一次

实测 · 本机EXPLAIN FORMAT=TREE输出(ORDER BY customer_id, created_at)
-> Sort: orders.customer_id, orders.created_at   ← 第二次排序,和窗口内部那次条件完全一样
    -> Table scan on <temporary>
        -> Temporary table
            -> Window aggregate with buffering: sum(...) OVER (...)
                -> Sort: orders.customer_id, orders.created_at   ← 第一次排序(窗口自己需要的)
                    -> Index range scan on orders using idx_cust ...
本以为外层ORDER BY customer_id, created_at和窗口子句里的PARTITION BY customer_id ORDER BY created_at排序条件完全一致,MySQL应该能直接复用第一次排序的结果,省掉第二次——真实测出来并不会。原因在中间那层Temporary table:窗口函数算完之后,结果必须先写进一张临时表(用来给每一行补上running_total这一列),而临时表的物理存储顺序和写入顺序未必一致,之前排好的顺序在这一步真实丢失了,所以哪怕外层ORDER BY条件和窗口子句字面完全相同,MySQL还是老老实实又排了一次。

验证:真实的耗时差距——"一样"的ORDER BY离"不用排"更近还是离"真不一样"更近

实测 · 本机EXPLAIN ANALYZE,5万行全表
不加ORDER BY:                        总耗时约 92.3 ms(只排1次)
ORDER BY customer_id, created_at(一样): 总耗时约 96.3 ms(排2次,但都很快)
ORDER BY amount DESC(真不一样):        总耗时约 197  ms(排2次,第二次要重新按amount全表排序)

结构上,"一样"的ORDER BY和"真不一样"的ORDER BY都要走两次Sort,属于同一类执行计划;但真实耗时上,"一样"的ORDER BY(96.3ms)反而更接近不排序的基线(92.3ms),因为它的第二次排序处理的是窗口函数已经按同样顺序整理过的数据,排序开销很小;而真不一样的ORDER BY(197ms)第二次要按一个全新的列(amount)对全表重新排序,真实多花了一倍还多的时间。也就是说:即使执行计划的"形状"一样(都是两次Sort),耗时可以差出一倍以上,看执行计划的步骤数量不能代替看真实的EXPLAIN ANALYZE耗时。

分步演示:三种ORDER BY写法的真实代价

每一步都是本文上面用EXPLAIN FORMAT=TREE和EXPLAIN ANALYZE跑出来的原始结果。

'写得一样'不等于'省掉一次排序'
这一步新验证的ORDER BY写法

    本文用到的工具

    • 本机 MySQL 26.7.0(InnoDB),5万行orders,SUM() OVER (PARTITION BY ... ORDER BY ...)窗口函数
    • EXPLAIN FORMAT=TREE:确认Sort出现的次数和位置
    • EXPLAIN ANALYZE:对比三种ORDER BY写法的真实耗时
    京ICP备2026067502号-1京公网安备11010502063487号