登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  MySQL

EXPLAIN FORMAT=JSON 怎样读取窗口节点

来源:17golang原创

时间:2026-10-25 10:58:41 311浏览 收藏

排查 MySQL 窗口函数时,传统 EXPLAIN 表格往往只能让你看到表访问路径,却看不清窗口计算本身。直接使用 EXPLAIN FORMAT=JSON,重点沿着 query_block.windowing.windows[] 读取,就能看到窗口函数、排序键和临时表信息。

官方地址:https://dev.mysql.com/doc/refman/8.4/en/window-function-optimization.html

要点速览
  • windowing 位于对应的 query_block 下,windows 数组里的每个对象描述一个窗口处理组。
  • functions 说明计算了哪些窗口函数,using_filesort 和 filesort_key 说明是否需要按窗口定义排序。
  • using_temporary_table 只能说明该窗口步骤使用了临时表,不能单独证明查询一定慢。

先让 EXPLAIN 输出窗口节点

先准备一个按部门分区、按薪资排序的查询。这里故意放入两个窗口函数,便于观察 windows 数组如何描述窗口计算:

-- 用一个稳定的员工编号作为并列薪资时的补充排序键。
EXPLAIN FORMAT=JSON
SELECT department_id, employee_id, salary,
       ROW_NUMBER() OVER (
           PARTITION BY department_id
           ORDER BY salary DESC, employee_id
       ) AS department_rank,
       SUM(salary) OVER (
           PARTITION BY department_id
       ) AS department_total
FROM employee_salary
WHERE employment_status = 'active';

结果最外层通常是 query_block。窗口函数对应的执行信息在它的 windowing 节点中,继续向下看 windows 数组,而不是只盯着最外层的 table。

EXPLAIN FORMAT=JSON 从 query_block 到 windowing windows 数组的静态结构说明图
图1:窗口节点结构说明图,展示 SQL 窗口定义与 JSON 计划字段的对应关系,不是运行截图。

按 windows 数组读取一个窗口对象

官方示例中的窗口对象会出现 name、functions、using_filesort、filesort_key 和 using_temporary_table 等字段。可以按下面的顺序读:

字段先回答的问题阅读重点
name它对应哪个命名窗口?没有使用 WINDOW 命名时可能为空字符串。
functions这一组计算了什么?与 ROW_NUMBER、SUM 等窗口函数对照。
using_filesort是否需要排序?为分区和窗口 ORDER BY 准备顺序。
filesort_key按什么排序?和 PARTITION BY、窗口内 ORDER BY 的列或表达式核对。
using_temporary_table是否用了临时表?结合数据量、窗口帧和整体计划判断影响。

不要把数组下标直接当成“第几个 SELECT 输出列”。一个窗口对象描述的是一组窗口处理信息,functions 才是与计算内容直接对应的字段;后面的 table 或 nested_loop 仍要单独阅读。

{
  "windowing": {
    "windows": [
      {
        "name": "",
        "using_temporary_table": true,
        "using_filesort": true,
        "filesort_key": ["`department_id`", "`salary` DESC"],
        "functions": ["row_number"]
      }
    ]
  }
}

上面的 JSON 只用于说明字段形状,不应把示例中的布尔值当成每个查询都会得到的固定结果。实际字段以当前语句和版本输出为准。

结合临时表和窗口顺序判断代价

using_filesort: true 表示窗口步骤需要排序,它不是“必然慢”的结论;要继续看排序键、输入行数和其他访问路径。using_temporary_table: true 表示窗口处理中使用了临时表,涉及需要保存或重新访问行的窗口帧时更值得关注。

如果查询包含多个窗口,不要只数数组元素。MySQL 会关注不同窗口的分区和排序要求;排序要求相同的窗口可以连续处理,后续窗口可能复用已有顺序。改写 SQL 时,可以把重复的窗口定义写成命名窗口,减少表达式分散带来的阅读成本:

-- 给相同的分区和排序定义命名,避免多个 OVER() 各写一套表达式。
EXPLAIN FORMAT=JSON
SELECT department_id, employee_id, salary,
       ROW_NUMBER() OVER department_window AS row_no,
       RANK() OVER department_window AS salary_rank
FROM employee_salary
WHERE employment_status = 'active'
WINDOW department_window AS (
    PARTITION BY department_id
    ORDER BY salary DESC, employee_id
);
窗口排序、临时表和多个窗口函数之间关系的静态说明图
图2:窗口代价阅读说明图,展示排序键、临时表与函数组之间的静态关系,不是运行截图。

用固定路径做排查记录

实际对比改写前后的计划时,我会把记录拆成四层:先确认 query_block,再确认是否有 windowing,接着逐个读取 windows[],最后回到同一节点下的 table、nested_loop 和成本信息。这样能避免把“访问表很快”和“窗口排序很轻”混成一个结论。

-- 用同一条语句保存改写前后的 JSON 计划,便于逐字段比较。
EXPLAIN FORMAT=JSON
SELECT department_id, employee_id,
       ROW_NUMBER() OVER (
           PARTITION BY department_id
           ORDER BY salary DESC, employee_id
       ) AS row_no
FROM employee_salary
WHERE employment_status = 'active';

如果没有看到 windowing,先确认当前语句真的含有窗口函数,并检查是否查看了正确的 query_block。如果有 windowing 但没有排序字段,不要自行补猜;只记录当前版本实际返回的字段。MySQL 文档明确指出,窗口执行信息过于丰富,传统输出无法完整展示,因此读取窗口节点应以 JSON 计划为主。

常见问题

为什么传统 EXPLAIN 看不到窗口函数的详细信息?

窗口处理信息较丰富,传统表格格式无法完整承载;应改用 EXPLAIN FORMAT=JSON 查看 windowing。

看到 using_filesort 就一定要改 SQL 吗?

不一定。它只说明该窗口步骤需要排序,还要结合排序键、输入规模和整体访问路径判断。

多个窗口对象是不是就代表执行了多个全量排序?

不能这样直接推断。排序要求相同的窗口可能连续处理并复用顺序,应比较每个对象的分区和排序字段。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>