窗口函数执行更快是因为只做一次全表扫描、一次排序和一次线性遍历,而join或子查询会为外层每行重复触发内层逻辑;需配合partition by与order by的复合索引,且order by字段顺序及方向须严格匹配索引定义。

ROW_NUMBER()、SUM() OVER、LAG() 这类窗口函数执行更快,不是因为“语法更高级”,而是 MySQL 8.0 的执行引擎只做一次全表扫描 + 一次排序 + 一次线性遍历,而传统 JOIN 或子查询会为外层每一行重复触发内层逻辑。
窗口函数只扫一次表,自连接可能扫 N 次
查“每个客户的最新订单”,用 JOIN 或 NOT EXISTS 写法,MySQL 必须对外层每条客户记录,都重新扫描订单表匹配最大时间戳——10 万客户 ≈ 10 万次独立扫描。窗口函数只需一次扫描,再按 customer_id 分区、按 order_time 排序,之后所有 ROW_NUMBER() 计算都在内存流中完成。
-
EXPLAIN里看到Using filesort和Using temporary各出现一次,是正常准备阶段 - 自连接的
EXPLAIN中,这两个提示会随外层行数反复出现,loops值直接等于外层行数 - 没索引时,自连接可能触发 10 万次
filesort;窗口函数只排序一次,哪怕分 100 个组,也只排 100 次(仍远少于 10 万)
PARTITION BY + ORDER BY 索引必须配对才有效
窗口函数不靠“避免排序”提速,而是让排序更快。单建 INDEX(customer_id) 没用;必须建复合索引,且字段顺序要严格匹配 OVER 子句:
- 写
PARTITION BY customer_id ORDER BY order_time DESC→ 索引应为(customer_id, order_time DESC) - 只建
INDEX(order_time),MySQL 仍得对每个客户分区单独排序 -
ORDER BY order_time DESC要生效,索引定义里必须显式写DESC(MySQL 8.0+ 支持) - 时间字段重复很常见,
ORDER BY order_time不稳,建议补上唯一列:ORDER BY order_time DESC, order_id DESC
LAG() 和 LEAD() 不依赖物理 ID 连续性
有人用 JOIN t2 ON t2.id = t1.id + 1 模拟前后行,但生产环境 id 绝对不连续——删数据、批量插入、分布式主键都会崩掉这个假设。
-
LAG(amount) OVER (PARTITION BY user_id ORDER BY event_time)只认ORDER BY定义的逻辑顺序,与存储无关 - 没写
ORDER BY直接用LAG()?MySQL 报错:ERROR 3589 (HY000): Window '<unnamed>' requires an ORDER BY clause</unnamed> -
LAG(amount, 1, 0)第三个参数是默认值,前一行不存在时填0,避免NULL导致后续计算中断(比如amount - LAG(amount)得NULL) - 时间字段有重复且没二级排序,结果不可复现——同一秒多条记录,MySQL 每次选的“前一行”可能不同
不能在 WHERE 里直接用窗口函数,但嵌套开销几乎为零
WHERE RANK() OVER (...) > 10 会报语法错误,这不是缺陷,而是 SQL 执行顺序决定的:WHERE 在 SELECT 阶段之前,而窗口函数属于 SELECT 计算环节。
- 正确写法是套一层子查询或 CTE:
SELECT * FROM (SELECT *, RANK() OVER (...) AS rnk FROM t) AS tmp WHERE rnk - 这个嵌套不增加实质开销:窗口计算已产出全部结果,过滤只是最后切片
- 自连接若想实现“每部门 Top 3”,往往得先聚合再关联,中间结果集更大、内存占用更高
- 别省略
ROWS:写SUM() OVER (ORDER BY date)默认是RANGE,同一天多笔订单会被重复累加;要严格按行序,必须显式写ROWS UNBOUNDED PRECEDING
真正容易被忽略的是:窗口函数的性能瓶颈几乎全在排序阶段,而排序是否能走索引,取决于 PARTITION BY 和 ORDER BY 字段是否构成可下推的复合索引——不是“有没有索引”,而是“索引字段顺序和方向是否与 OVER 子句完全一致”。











